5 * Functions wrapping php-GACL's functionality, to create a generic OpenEMR access control system.
7 * LICENSE: This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 3
10 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see
17 * http://www.gnu.org/licenses/licenses.html#GPL .
20 * @license http://www.gnu.org/licenses/licenses.html#GPL GNU GPL V3+
21 * @author Brady Miller <brady@sparmy.com>
22 * @author Rod Roark <rod@sunsetsystems.com>
23 * @link http://www.open-emr.org
26 // php-GACL access controls are included in OpenEMR. The below
27 // function will automatically create the path where gacl.class.php
28 // can be found. Note that this path can be manually set below
29 // for users who are using an external version of php-GACL.
30 // Also note that php-GACL access controls can be turned off
33 $phpgacl_location = dirname(__FILE__).'/../gacl';
35 // If using an external version of phpGACL, then uncomment the following
36 // line and manually place the path below. IN THIS CASE YOU MUST ALSO
37 // COMMENT OUT ABOVE $phpgacl_location ASSIGNMENT ABOVE, OR BACKUPS WILL
38 // NOT RESTORE PROPERLY!
40 //$phpgacl_location = "/var/www/gacl";
42 // If you want to turn off php-GACL, then uncomment the following line.
43 // IN THIS CASE YOU MUST ALSO COMMENT OUT ABOVE $phpgacl_location ASSIGNMENT(S)
44 // ABOVE, OR BACKUPS WILL NOT RESTORE PROPERLY!
46 //unset($phpgacl_location);
49 // The following Access Control Objects (ACO) are currently supported.
50 // These are the "things to be protected":
52 // Section "admin" (Administration):
53 // super Superuser - can delete patients, encounters, issues
54 // calendar Calendar Settings
55 // database Database Reporting
56 // forms Forms Administration
57 // practice Practice Settings
58 // superbill Superbill Codes Administration
59 // users Users/Groups/Logs Administration
60 // batchcom Batch Communication Tool
61 // language Language Interface Tool
62 // drugs Pharmacy Dispensary
63 // acl ACL Administration
65 // Section "acct" (Accounting):
66 // bill Billing (write optional)
67 // disc Allowed to discount prices (in Fee Sheet or Checkout form)
69 // rep Financial Reporting - my encounters
70 // rep_a Financial Reporting - anything
72 // Section "patients" (Patient Information):
73 // appt Appointments (write,wsome optional)
74 // demo Demographics (write,addonly optional)
75 // med Medical Records and History (write,addonly optional)
76 // trans Transactions, e.g. referrals (write optional)
77 // docs Documents (write,addonly optional)
78 // notes Patient Notes (write,addonly optional)
79 // sign Sign Lab Results (write,addonly optional)
81 // Section "encounters" (Encounter Information):
82 // auth Authorize - my encounters
83 // auth_a Authorize - any encounters
84 // coding Coding - my encounters (write,wsome optional)
85 // coding_a Coding - any encounters (write,wsome optional)
86 // notes Notes - my encounters (write,addonly optional)
87 // notes_a Notes - any encounters (write,addonly optional)
88 // date_a Fix encounter dates - any encounters
89 // relaxed Less-private information (write,addonly optional)
90 // (e.g. the Sports Fitness encounter form)
92 // Section "squads" applies to sports team use only:
93 // acos in this section define the user-specified list of squads
95 // Section "sensitivities" (Sensitivities):
99 // Section "lists" (Lists):
100 // default Default List (write,addonly optional)
101 // state State List (write,addonly optional)
102 // country Country List (write,addonly optional)
103 // language Language List (write,addonly optional)
104 // ethrace Ethnicity-Race List (write,addonly optional)
106 // Section "placeholder" (Placeholder):
107 // filler Placeholder (Maintains empty ACLs)
109 // Section "nationnotes" (Nation Notes):
110 // nn_configure Nation Notes
112 // Section "patientportal" (Patient Portal):
113 // portal Patient Portal
115 if (isset ($phpgacl_location)) {
116 $GLOBALS['phpgacl_location_global'] = $phpgacl_location;
117 require_once("$phpgacl_location/gacl.class.php");
118 $gacl_object = new gacl();
119 //DO NOT CHANGE BELOW VARIABLE
120 $section_aro_value = 'users';
121 $GLOBALS['section_aro_value_global'] = $section_aro_value;
125 * Check if a user has a given type or types of access to an access control object.
127 * Globals that can use are:
128 * $GLOBALS['phpgacl_location_global']
129 * $GLOBALS['section_aro_value_global']
131 * This function will check for access to the given ACO.
132 * view - The user may view but not add or modify entries
133 * write - The user may add or modify the ACO
134 * wsome - The user has limited add/modify access to the ACO
135 * addonly - The user may view and add but not modify entries
137 * @param string $section Category of ACO
138 * @param string $value Subcategory of ACO
139 * @param string $user Optional user being checked for access.
140 * @param string|array $return_value Type or types of access being requested.
141 * @return bool|array FALSE if access is denied, TRUE if allowed. An
142 * array() of bools is returned if $return_value is an
143 * array, representing results for each type of access
146 function acl_check($section, $value, $user = '', $return_value = '') {
147 if (! $user) $user = $_SESSION['authUser'];
149 if ($GLOBALS['phpgacl_location_global']) {
150 // This will return all pertinent ACL's (including return_values and whether allow/deny)
151 // Walk through them to assess for access
152 $gacl_object = new gacl();
153 $acl_results = $gacl_object->acl_query($section, $value, $GLOBALS['section_aro_value_global'], $user,NULL,NULL,NULL,NULL,NULL,TRUE);
154 if (empty($acl_results)) {
155 return FALSE; //deny access
157 $access=FALSE; //flag
159 foreach ($acl_results as $acl_result) {
160 if (empty($acl_result['acl_id'])) return FALSE; //deny access, since this happens if no pertinent ACL's are returned
161 if (is_array($return_value)) {
162 foreach ($return_value as $single_return_value) {
163 if (empty($single_return_value)) {
164 // deal with case if not looking for specific return value
165 if ($acl_result['allow']) {
172 else { //!empty($single_return_value)
173 // deal with case if looking for specific return value
174 if ($acl_result['return_value'] == $single_return_value) {
175 if ($acl_result['allow']) {
185 else { // $return_value is not an array (either empty or with one value)
186 if (empty($return_value)) {
187 // deal with case if not looking for specific return value
188 if ($acl_result['allow']) {
195 else { //!empty($return_value)
196 // deal with case if looking for specific return value
197 if ($acl_result['return_value'] == $return_value) {
198 if ($acl_result['allow']) {
209 // Now decide whether user has access
210 // (Note a denial takes precedence)
211 if ($deny) return FALSE;
212 if ($access) return TRUE;
217 // If no phpgacl, then apply the old static rules whereby "authorized"
218 // users (providers) can do anything, and other users can do most things.
219 // If you want custom access control but don't want to mess with phpGACL,
220 // then you could customize the code below instead.
222 if ($user == 'admin') return 'write';
223 if ($section == 'admin' && $value == 'super') return 0;
224 if ($_SESSION['userauthorized']) return 'write';
226 if ($section == 'patients') {
227 if ($value == 'med') return 1;
230 else if ($section == 'encounters') {
231 if (strpos($value, 'coding' ) === 0) return 'write';
232 if (strpos($value, 'notes' ) === 0) return 'write';
233 if ($value == 'relaxed') return 'write';
235 else if ($section != 'admin') {
242 // Get the ACO name/value pairs for a designated section. Each value
243 // is an array (section_value, value, order_value, name, hidden).
245 function acl_get_section_acos($section) {
246 global $phpgacl_location;
247 if ($phpgacl_location) {
248 include_once("$phpgacl_location/gacl_api.class.php");
249 $gacl = new gacl_api();
250 $arr1 = $gacl->get_objects($section, 1, 'ACO');
252 if (!empty($arr1[$section])) {
253 foreach ($arr1[$section] as $value) {
254 $odata = $gacl->get_object_data($gacl->get_object_id($section, $value, 'ACO'), 'ACO');
255 $arr[$value] = $odata[0];
263 // Sort squads by their order value. Used only by acl_get_squads().
264 function _acl_squad_compare($a, $b) {
265 if ($a[2] == $b[2]) {
266 // If order value is the same, sort by squad name.
267 if ($a[3] == $b[3]) return 0;
268 return ($a[3] < $b[3]) ? -1 : 1;
270 return ($a[2] < $b[2]) ? -1 : 1;
273 // Return an array keyed on squad ACO names.
274 // This is only applicable for sports team use.
276 function acl_get_squads() {
277 $squads = acl_get_section_acos('squads');
278 uasort($squads, "_acl_squad_compare");
282 // Return an array keyed on encounter sensitivity level ACO names.
283 // Sensitivities are useful when some encounter notes are not
284 // medically sensitive (e.g. a physical fitness test), and/or if
285 // some will be "for doctor's eyes only" (e.g. STD treatment).
287 // When a non-blank sensitivity value exists in the new encounter
288 // form, it names an additional ACO required for access to all forms
289 // in the encounter. If you want some encounters to be non-sensitive,
290 // then you also need some default nonblank sensitivity for normal
291 // encounters, as well as greater encounter notes permissions for
292 // those allowed to view non-sensitive encounters.
294 function acl_get_sensitivities() {
295 return acl_get_section_acos('sensitivities');
299 // Returns true if aco exist
300 // Returns false if aco doesn't exist
301 // $section_name = name of section (string)
302 // $aco_name = name of aco (string)
304 function aco_exist($section_name, $aco_name) {
305 global $phpgacl_location;
306 if (isset ($phpgacl_location)) {
307 include_once("$phpgacl_location/gacl_api.class.php");
308 $gacl = new gacl_api();
309 $aco_id = $gacl->get_object_id($section_name, $aco_name, 'ACO');
318 // Returns a sorted array of all available Group Titles.
320 function acl_get_group_title_list() {
321 global $phpgacl_location;
322 if (isset ($phpgacl_location)) {
323 include_once("$phpgacl_location/gacl_api.class.php");
324 $gacl = new gacl_api();
325 $parent_id = $gacl->get_root_group_id();
326 $arr_group_ids = $gacl->get_group_children($parent_id, 'ARO', 'RECURSE');
327 $arr_group_titles = array();
328 foreach ($arr_group_ids as $value) {
329 $arr_group_data = $gacl->get_group_data($value, 'ARO');
330 $arr_group_titles[$value] = $arr_group_data[3];
332 sort($arr_group_titles);
333 return $arr_group_titles;
339 // Returns a sorted array of group Titles that a user belongs to.
340 // Returns 0 if does not belong to any group yet.
341 // $user_name = Username, which is login name.
343 function acl_get_group_titles($user_name) {
344 global $phpgacl_location, $section_aro_value;
345 if (isset ($phpgacl_location)) {
346 include_once("$phpgacl_location/gacl_api.class.php");
347 $gacl = new gacl_api();
348 $user_aro_id = $gacl->get_object_id($section_aro_value, $user_name, 'ARO');
350 $arr_group_id = $gacl->get_object_groups($user_aro_id, 'ARO', 'NO_RECURSE');
352 foreach ($arr_group_id as $key => $value) {
353 $arr_group_data = $gacl->get_group_data($value, 'ARO');
354 $arr_group_titles[$key] = $arr_group_data[3];
356 sort($arr_group_titles);
357 return $arr_group_titles;
365 // This will place the user aro object into selected group(s)
366 // It uses the set_user_aro() function
367 // $username = username (string)
368 // $group = title of group(s) (string or array)
370 function add_user_aros($username, $group) {
371 $current_user_groups = acl_get_group_titles($username);
372 if (!$current_user_groups) {
373 $current_user_groups = array();
375 if (is_array($group)){
376 foreach ($group as $value) {
377 if (!in_array($value, $current_user_groups)) {
378 array_push($current_user_groups, $value);
383 if (!in_array($group, $current_user_groups)) {
384 array_push($current_user_groups, $group);
387 $user_data = mysql_fetch_array(sqlStatement("select * from users where username='" .
389 set_user_aro($current_user_groups, $username, $user_data["fname"],
390 $user_data["mname"], $user_data["lname"]);
395 // This will remove the user aro object from the selected group(s)
396 // It uses the set_user_aro() function
397 // $username = username (string)
398 // $group = title of group(s) (string or array)
400 function remove_user_aros($username, $group) {
401 $current_user_groups = acl_get_group_titles($username);
402 $new_user_groups = array();
403 if (is_array($group)){
404 foreach ($current_user_groups as $value) {
405 if (!in_array($value, $group)) {
406 array_push($new_user_groups, $value);
411 foreach ($current_user_groups as $value) {
412 if ($value != $group) {
413 array_push($new_user_groups, $value);
417 $user_data = mysql_fetch_array(sqlStatement("select * from users where username='" .
419 set_user_aro($new_user_groups, $username, $user_data["fname"],
420 $user_data["mname"], $user_data["lname"]);
425 // This will either create or edit a user aro object, and then place it
426 // in the requested groups. It will not allow removal of the 'admin'
427 // user or gacl_protected users from the 'admin' group.
428 // $arr_group_titles = titles of the groups that user will be added to.
429 // $user_name = username, which is login name.
430 // $first_name = first name
431 // $middle_name = middle name
432 // $last_name = last name
434 function set_user_aro($arr_group_titles, $user_name, $first_name, $middle_name, $last_name) {
435 global $phpgacl_location, $section_aro_value;
437 if (isset ($phpgacl_location)) {
438 include_once("$phpgacl_location/gacl_api.class.php");
439 $gacl = new gacl_api();
441 //see if this user is gacl protected (ie. do not allow
442 //removal from the Administrators group)
443 require_once(dirname(__FILE__).'/user.inc');
444 require_once(dirname(__FILE__).'/calendar.inc');
445 $userNametoID = getIDfromUser($user_name);
446 if (checkUserSetting("gacl_protect","1",$userNametoID) || $user_name == "admin") {
447 $gacl_protect = true;
450 $gacl_protect = false;
453 //get array of all available group ID numbers
454 $parent_id = $gacl->get_root_group_id();
455 $arr_all_group_ids = $gacl->get_group_children($parent_id, 'ARO', 'RECURSE');
457 //Cycle through ID array to find and process each selected group
458 //Create a counter since processing of first hit is unique
460 foreach ($arr_all_group_ids as $value) {
461 $arr_group_data = $gacl->get_group_data($value, 'ARO');
462 if ((empty($arr_group_titles)) ||
463 (in_array($arr_group_data[3], $arr_group_titles))) {
464 //We have a hit, so need to add group and increment counter
465 // because processing of first hit is unique
466 //This will also deal with an empty $arr_group_titles array
467 // removing user from all groups unless 'admin'
468 $counter = $counter + 1;
469 //create user full name field
471 $full_name = $first_name . " " . $middle_name . " " . $last_name;
475 $full_name = $first_name . " " . $last_name;
478 $full_name = $first_name;
482 //If this is not the first group to be added, then will skip below
483 // and will be added. If this is the first group, then need to
484 // go thru several steps before adding the group.
486 //get ID of user ARO object, if it exist
487 $user_aro_id = $gacl->get_object_id($section_aro_value, $user_name, 'ARO');
489 //user ARO object already exist, so will edit it
490 $gacl->edit_object($user_aro_id, $section_aro_value, $full_name, $user_name, 10, 0, 'ARO');
492 //remove all current user ARO object group associations
493 $arr_remove_group_ids = $gacl->get_object_groups($user_aro_id, 'ARO', 'NO_RECURSE');
494 foreach ($arr_remove_group_ids as $value2) {
495 $gacl->del_group_object($value2, $section_aro_value, $user_name, 'ARO');
499 //user ARO object does not exist, so will create it
500 $gacl->add_object($section_aro_value, $full_name, $user_name, 10, 0, 'ARO');
504 //place the user ARO object in the selected group (if group(s) is selected)
505 if (!empty($arr_group_titles)) {
506 $gacl->add_group_object($value, $section_aro_value, $user_name, 'ARO');
510 //Below will not allow 'admin' or gacl_protected user to be removed from 'admin' group
514 $admin_id = $gacl->get_object_id($section_aro_value, $user_name, 'ARO');
515 $arr_admin = $gacl->get_object_groups($admin_id, 'ARO', 'NO_RECURSE');
516 foreach ($arr_admin as $value3) {
517 $arr_admin_data = $gacl->get_group_data($value3, 'ARO');
518 if (strcmp($arr_admin_data[2], 'admin') == 0) {
522 if (!$boolean_admin) {
523 foreach ($arr_all_group_ids as $value4) {
524 $arr_temp = $gacl->get_group_data($value4, 'ARO');
525 if ($arr_temp[2] == 'admin') {
526 $gacl->add_group_object($value4, $section_aro_value, $user_name, 'ARO');
532 //if array of groups was empty, then we are done, and can break from loop
533 if (empty($arr_group_titles)) break;
541 // Returns true if acl exist
542 // Returns false if acl doesn't exist
543 // EITHER $title or $name is required(send FALSE in variable
544 // not being used). If both are sent, then only $title will be
546 // $return_value is required
547 // $title = title of acl (string)
548 // $name = name of acl (string)
549 // $return_value = return value of acl (string)
551 function acl_exist($title, $name, $return_value) {
552 global $phpgacl_location;
553 if (isset ($phpgacl_location)) {
554 include_once("$phpgacl_location/gacl_api.class.php");
555 $gacl = new gacl_api();
557 $acl = $gacl->search_acl(FALSE, FALSE, FALSE, FALSE, $title, FALSE, FALSE, FALSE, $return_value);
560 $group_id = $gacl->get_group_id($name, NULL, 'ARO');
562 $group_data = $gacl->get_group_data($group_id, 'ARO');
563 $acl = $gacl->search_acl(FALSE, FALSE, FALSE, FALSE, $group_data[3], FALSE, FALSE, FALSE, $return_value);
570 $acl = $gacl->search_acl(FALSE, FALSE, FALSE, FALSE, $title, FALSE, FALSE, FALSE, $return_value);
582 // This will add a new acl and group(if group doesn't yet exist)
583 // with one aco in it.
584 // $acl_title = title of acl (string)
585 // $acl_name = name of acl (string)
586 // $return_value = return value of acl (string)
587 // $note = description of acl (array)
589 function acl_add($acl_title, $acl_name, $return_value, $note) {
590 global $phpgacl_location;
591 if (isset ($phpgacl_location)) {
592 include_once("$phpgacl_location/gacl_api.class.php");
593 $gacl = new gacl_api();
594 $group_id = $gacl->get_group_id($acl_name, $acl_title, 'ARO');
596 //group already exist, so just create acl
597 $gacl->add_acl(array("placeholder"=>array("filler")),
598 NULL, array($group_id), NULL, NULL, 1, 1, $return_value, $note);
601 //create group, then create acl
602 $parent_id = $gacl->get_root_group_id();
603 $aro_id = $gacl->add_group($acl_name, $acl_title, $parent_id, 'ARO');
604 $gacl->add_acl(array("placeholder"=>array("filler")),
605 NULL, array($aro_id), NULL, NULL, 1, 1, $return_value, $note);
613 // This will remove acl. It will also remove group(if the group
614 // is no longer associated with any acl's).
615 // $acl_title = title of acl (string)
616 // $acl_name = name of acl (string)
617 // $return_value = return value of acl (string)
618 // $note = description of acl (array)
620 function acl_remove($acl_title, $return_value) {
621 global $phpgacl_location;
622 if (isset ($phpgacl_location)) {
623 include_once("$phpgacl_location/gacl_api.class.php");
624 $gacl = new gacl_api();
625 //First, delete the acl
626 $acl_id=$gacl->search_acl(FALSE, FALSE, FALSE, FALSE, $acl_title, FALSE, FALSE, FALSE, $return_value);
627 $gacl->del_acl($acl_id[0]);
628 //Then, remove the group(if no more acl's are remaining)
629 $acl_search=$gacl->search_acl(FALSE, FALSE, FALSE, FALSE, $acl_title, FALSE, FALSE, FALSE, FALSE);
630 if (empty($acl_search)){
631 $group_id=$gacl-> get_group_id(NULL, $acl_title, 'ARO');
632 $gacl->del_group($group_id, TRUE, 'ARO');
640 // This will place the aco(s) into the selected acl
641 // $acl_title = title of acl (string)
642 // $return_value = return value of acl (string)
643 // $aco_id = id of aco (array)
645 function acl_add_acos($acl_title, $return_value, $aco_id) {
646 global $phpgacl_location;
647 if (isset ($phpgacl_location)) {
648 include_once("$phpgacl_location/gacl_api.class.php");
649 $gacl = new gacl_api();
650 $acl_id = $gacl->search_acl(FALSE, FALSE, FALSE, FALSE, $acl_title, FALSE, FALSE, FALSE, $return_value);
651 foreach ($aco_id as $value) {
652 $aco_data = $gacl->get_object_data($value, 'ACO');
653 $aco_section = $aco_data[0][0];
654 $aco_name = $aco_data[0][1];
655 $gacl->append_acl($acl_id[0], NULL, NULL, NULL, NULL, array($aco_section=>array($aco_name)));
663 // This will remove the aco(s) from the selected acl
664 // Note if all aco's are removed, then will place the filler-placeholder
665 // into the acl to avoid complete removal of the acl.
666 // $acl_title = title of acl (string)
667 // $return_value = return value of acl (string)
668 // $aco_id = id of aco (array)
670 function acl_remove_acos($acl_title, $return_value, $aco_id) {
671 global $phpgacl_location;
672 if (isset ($phpgacl_location)) {
673 include_once("$phpgacl_location/gacl_api.class.php");
674 $gacl = new gacl_api();
675 $acl_id = $gacl->search_acl(FALSE, FALSE, FALSE, FALSE, $acl_title, FALSE, FALSE, FALSE, $return_value);
677 // Check to see if removing all acos. If removing all acos then will
678 // ensure the filler-placeholder aco in acl to avoid complete
679 // removal of the acl.
680 if (count($aco_id) == acl_count_acos($acl_title, $return_value)) {
681 //1-get the filler-placeholder aco id
682 $filler_aco_id = $gacl->get_object_id('placeholder','filler','ACO');
683 //2-add filler-placeholder aco
684 acl_add_acos($acl_title, $return_value, array($filler_aco_id));
685 //3-ensure filler-placeholder aco is not to be deleted
686 $safeListaco = remove_element($_POST["selection"],$filler_aco_id);
687 //4-prepare to safely delete the acos
688 $aco_id = $safeListaco;
691 foreach ($aco_id as $value) {
692 $aco_data = $gacl->get_object_data($value, 'ACO');
693 $aco_section = $aco_data[0][0];
694 $aco_name = $aco_data[0][1];
695 $gacl->shift_acl($acl_id[0], NULL, NULL, NULL, NULL, array($aco_section=>array($aco_name)));
703 // This will return the number of aco objects
704 // in a specified acl.
705 // $acl_title = title of acl (string)
706 // $return_value = return value of acl (string)
708 function acl_count_acos($acl_title, $return_value) {
709 global $phpgacl_location;
710 if (isset ($phpgacl_location)) {
711 include_once("$phpgacl_location/gacl_api.class.php");
712 $gacl = new gacl_api();
713 $acl_id = $gacl->search_acl(FALSE, FALSE, FALSE, FALSE, $acl_title, FALSE, FALSE, FALSE, $return_value);
714 $acl_data = $gacl->get_acl($acl_id[0]);
716 foreach ($acl_data['aco'] as $key => $value) {
717 $aco_count = $aco_count + count($acl_data['aco'][$key]);
725 // Function to remove an element from an array
727 function remove_element($arr, $val){
729 foreach ($arr as $value){
730 if ($value != $val) {
731 array_push($arr2,$value);