Fully responsive globals.php with vertical menu (#2460)
[openemr.git] / interface / main / main_screen.php
blob3c251780124f7af858188b5c48c6f0aa6bad1a47
1 <?php
2 /**
3 * The outside frame that holds all of the OpenEMR User Interface.
5 * @package OpenEMR
6 * @link http://www.open-emr.org
7 * @author Rod Roark <rod@sunsetsystems.com>
8 * @author Brady Miller <brady.g.miller@gmail.com>
9 * @author Ranganath Pathak <pathak@scrs1.org>
10 * @copyright Copyright (c) 2018 Rod Roark <rod@sunsetsystems.com>
11 * @copyright Copyright (c) 2018-2019 Brady Miller <brady.g.miller@gmail.com>
12 * @copyright Copyright (c) 2019 Ranganath Pathak <pathak@scrs1.org>
13 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
16 /* Include our required headers */
17 require_once('../globals.php');
19 use OpenEMR\Common\Crypto\CryptoGen;
20 use OpenEMR\Common\Utils\RandomGenUtils;
21 use OpenEMR\Core\Header;
22 use OpenEMR\Services\FacilityService;
23 use u2flib_server\U2F;
25 ///////////////////////////////////////////////////////////////////////
26 // Functions to support MFA.
27 ///////////////////////////////////////////////////////////////////////
29 function posted_to_hidden($name)
31 if (isset($_POST[$name])) {
32 echo "<input type='hidden' name='" . attr($name) . "' value='" . attr($_POST[$name]) . "' />\r\n";
36 function generate_html_start()
39 <html>
40 <head>
41 <?php Header::setupHeader(); ?>
42 <title><?php echo xlt("MFA Authorization"); ?></title>
43 <style>
44 .alert-msg {
45 font-size:100%;
46 font-weight:700;
48 </style>
49 <?php
52 function generate_html_u2f()
54 global $appId;
56 <script src="<?php echo $GLOBALS['webroot'] ?>/library/js/u2f-api.js"></script>
57 <script>
58 function doAuth() {
59 var f = document.getElementById("u2fform");
60 var requests = JSON.parse(f.form_requests.value);
61 // The server's getAuthenticateData() repeats the same challenge in all requests.
62 var challenge = requests[0].challenge;
63 var registeredKeys = new Array();
64 for (var i = 0; i < requests.length; ++i) {
65 registeredKeys[i] = {"version": requests[i].version, "keyHandle": requests[i].keyHandle};
67 u2f.sign(
68 <?php echo js_escape($appId); ?>,
69 challenge,
70 registeredKeys,
71 function (data) {
72 if (data.errorCode && data.errorCode != 0) {
73 alert(<?php echo xlj("Key access failed with error"); ?> +' ' + data.errorCode);
74 return;
76 f.form_response.value = JSON.stringify(data);
77 f.submit();
83 </script>
84 <?php
86 function input_focus()
89 <script>
90 $(function() {
91 $('#totp').focus();
92 });
93 </script>
95 <?php
98 function generate_html_top()
100 echo '</head>';
101 echo '<body>';
104 function generate_html_middle()
106 posted_to_hidden('new_login_session_management');
107 posted_to_hidden('authProvider');
108 posted_to_hidden('languageChoice');
109 posted_to_hidden('authUser');
110 posted_to_hidden('clearPass');
113 function generate_html_end()
115 echo "</div></body></html>\n";
116 session_unset();
117 session_destroy();
118 unset($_COOKIE[session_name()]);
119 return 0;
122 ///////////////////////////////////////////////////////////////////////
123 // Begin code to support U2F and APP Based TOTP logic.
124 ///////////////////////////////////////////////////////////////////////
125 $errormsg = '';
126 $regs = array(); // for mapping device handles to their names
127 $registrations = array(); // the array of stored registration objects
128 $res1 = sqlStatement(
129 "SELECT a.name, a.method, a.var1 FROM login_mfa_registrations AS a " .
130 "WHERE a.user_id = ? AND (a.method = 'TOTP' OR a.method = 'U2F') ORDER BY a.name",
131 array($_SESSION['authId'])
134 $registrationAttempt = false;
135 $isU2F = false;
136 $isTOTP = false;
137 while ($row1 = sqlFetchArray($res1)) {
138 $registrationAttempt = true;
139 if ($row1['method'] == 'U2F') {
140 $isU2F = true;
141 $regobj = json_decode($row1['var1']);
142 $regs[json_encode($regobj->keyHandle)] = $row1['name'];
143 $registrations[] = $regobj;
144 } else { // $row1['method'] == 'TOTP'
145 $isTOTP = true;
149 if ($registrationAttempt) {
150 $requests = '';
151 $errortype = '';
152 if ($isU2F) {
153 // There is at least one U2F key registered so we have to request or verify key data.
154 // https is required, and with a proxy the server might not see it.
155 $scheme = "https://"; // isset($_SERVER['HTTPS']) ? "https://" : "http://";
156 $appId = $scheme . $_SERVER['HTTP_HOST'];
157 $u2f = new u2flib_server\U2F($appId);
159 $userid = $_SESSION['authId'];
160 $form_response = empty($_POST['form_response']) ? '' : $_POST['form_response'];
161 if ($form_response) {
162 // TOTP METHOD enabled if TOTP is visible in post request
163 if (isset($_POST['totp']) && trim($_POST['totp']) != "" && $isTOTP) {
164 $errormsg = false;
166 $form_response = '';
168 $res1 = sqlQuery(
169 "SELECT a.var1 FROM login_mfa_registrations AS a WHERE a.user_id = ? AND a.method = 'TOTP'",
170 array($_SESSION['authId'])
172 $registrationSecret = false;
173 if (!empty($res1['var1'])) {
174 $registrationSecret = $res1['var1'];
177 // Decrypt the secret
178 // First, try standard method that uses standard key
179 $cryptoGen = new CryptoGen();
180 $secret = $cryptoGen->decryptStandard($registrationSecret);
181 if (empty($secret)) {
182 // Second, try the password hash, which was setup during install and is temporary
183 $passwordResults = privQuery(
184 "SELECT password FROM users_secure WHERE username = ?",
185 array($_POST["authUser"])
187 if (!empty($passwordResults["password"])) {
188 $secret = $cryptoGen->decryptStandard($registrationSecret, $passwordResults["password"]);
189 if (!empty($secret)) {
190 error_log("Disregard the decryption failed authentication error reported above this line; it is not an error.");
191 // Re-encrypt with the more secure standard key
192 $secretEncrypt = $cryptoGen->encryptStandard($secret);
193 privStatement(
194 "UPDATE login_mfa_registrations SET var1 = ? where user_id = ? AND method = 'TOTP'",
195 array($secretEncrypt, $userid)
201 if (!empty($secret)) {
202 $googleAuth = new Totp($secret);
203 $form_response = $googleAuth->validateCode($_POST['totp']);
206 if ($form_response) {
207 // Keep track of when challenges were last answered correctly.
208 privStatement(
209 "UPDATE users_secure SET last_challenge_response = NOW() WHERE id = ?",
210 array($_SESSION['authId'])
212 } else {
213 $errormsg = xl("The code you entered was not valid");
214 $errortype = "TOTP";
216 } elseif ($isU2F) { // Otherwise use U2F METHOD
217 // We have key data, check if it matches what was registered.
218 $tmprow = sqlQuery("SELECT login_work_area FROM users_secure WHERE id = ?", array($userid));
219 try {
220 $registration = $u2f->doAuthenticate(
221 json_decode($tmprow['login_work_area']), // these are the original challenge requests
222 $registrations,
223 json_decode($_POST['form_response'])
225 // Stored registration data needs to be updated because the usage count has changed.
226 // We have to use the matching registered key.
227 $strhandle = json_encode($registration->keyHandle);
228 if (isset($regs[$strhandle])) {
229 sqlStatement(
230 "UPDATE login_mfa_registrations SET `var1` = ? WHERE " .
231 "`user_id` = ? AND `method` = 'U2F' AND `name` = ?",
232 array(json_encode($registration), $userid, $regs[$strhandle])
234 } else {
235 error_log("Unexpected keyHandle returned from doAuthenticate(): '$strhandle'");
237 // Keep track of when challenges were last answered correctly.
238 sqlStatement(
239 "UPDATE users_secure SET last_challenge_response = NOW() WHERE id = ?",
240 array($_SESSION['authId'])
242 } catch (u2flib_server\Error $e) {
243 // Authentication failed so we will build the U2F form again.
244 $form_response = '';
245 $errormsg = xl('U2F Key Authentication error') . ": " . $e->getMessage();
246 $errortype = "U2F";
248 } else {
249 // do nothing
250 $form_response = '';
253 if (!$form_response) {
254 generate_html_start();
255 if ($isU2F) {
256 generate_html_u2f();
258 if ($isTOTP) {
259 input_focus();
261 generate_html_top();
262 if ($isTOTP) {
263 echo '<div class="container">';
264 echo '<div class="row">';
265 echo ' <div class="col-sm-12">';
266 echo ' <div class="page-header">';
267 echo ' <h2>' . xlt('TOTP Verification') . '</h2>';
268 echo ' </div>';
269 echo ' </div>';
270 echo '</div>';
271 if ($errormsg && $errortype == "TOTP") {
272 echo '<div class="row"><div class="col-sm-12"><div class="alert alert-danger alert-msg">' . text($errormsg) . '</div></div></div>';
275 echo '<div class="row">';
276 echo ' <div class="col-sm-12">';
277 echo ' <form method="post" action="main_screen.php?auth=login&site=' . attr_url($_GET['site']) . '" target="_top" name="challenge_form" id=="challenge_form">';
278 echo ' <fieldset>';
279 echo ' <legend>'. xlt('Provide TOTP code') .'</legend>';
280 echo ' <div class="form-group">';
281 echo ' <div class="col-sm-6 col-sm-offset-3">';
282 echo ' <label for="totp">' . xlt('Enter the code from your authentication application on your device') . ':</label>';
283 echo ' <input type="text" name="totp" class="form-control input-lg" id="totp" maxlength="12" required>';
284 echo ' <input type="hidden" name="form_response" value="true" />';
285 generate_html_middle();
286 echo ' </div>';
287 echo ' </fieldset>';
288 echo ' <div class="form-group clearfix">';
289 echo ' <div class="col-sm-12 text-left position-override">';
290 echo ' <button type="submit" class="btn btn-default btn-save">' . xlt('Authenticate TOTP') . '</button>';
291 echo ' </div>';
292 echo ' </div>';
293 echo ' </div>';
294 echo ' </form>';
295 echo ' </div>';
296 echo '</div>';
298 if ($isU2F) {
299 // There is no key data yet or authentication failed, so we need to solicit it.
300 $requests = json_encode($u2f->getAuthenticateData($registrations));
301 // Persist the challenge also in the database because the browser is untrusted.
302 sqlStatement(
303 "UPDATE users_secure SET login_work_area = ? WHERE id = ?",
304 array($requests, $userid)
307 echo '<div class="container">';
308 echo '<div class="row">';
309 echo ' <div class="col-sm-12">';
310 echo ' <div class="page-header">';
311 echo ' <h2>' . xlt('U2F Key Verification') . '</h2>';
312 echo ' </div>';
313 echo ' </div>';
314 echo '</div>';
315 if ($errormsg && $errortype == "U2F") {
316 echo '<div class="row"><div class="col-sm-12"><div class="alert alert-danger alert-msg">' . text($errormsg) . '</div></div></div>';
318 echo '<div class="row">';
319 echo ' <div class="col-sm-12">';
320 echo ' <form method="post" name="u2fform" id="u2fform" action="main_screen.php?auth=login&site=' . attr_url($_GET['site']) . '" target="_top">';
321 echo ' <fieldset>';
322 echo ' <legend>'. xlt('Insert U2F Key') .'</legend>';
323 echo ' <div class="form-group">';
324 echo ' <div class="col-sm-6 col-sm-offset-3">';
325 echo ' <ul>';
326 echo ' <li>' . xlt('Insert your key into a USB port and click the Authenticate button below.') . '</li>';
327 echo ' <li>' . xlt('Then press the flashing button on your key within 1 minute.') . '</li>';
328 echo ' </ul>';
329 echo ' </div>';
330 echo ' </fieldset>';
331 echo ' <div class="form-group clearfix">';
332 echo ' <div class="col-sm-12 text-left position-override">';
333 echo ' <button type="button" id="authutf" class="btn btn-default btn-save" onclick="doAuth()">' . xlt('Authenticate U2F') . '</button>';
334 echo ' <input type="hidden" name="form_requests" value="' . attr($requests) . '" />';
335 echo ' <input type="hidden" name="form_response" value="" />';
336 generate_html_middle();
337 echo ' </div>';
338 echo ' </div>';
339 echo ' </form>';
340 echo ' </div>';
341 echo '</div>';
343 exit(generate_html_end());
347 ///////////////////////////////////////////////////////////////////////
348 // End of U2F and APP Based TOTP logic.
349 ///////////////////////////////////////////////////////////////////////
351 // Creates a new session id when load this outer frame
352 // (allows creations of separate OpenEMR frames to view patients concurrently
353 // on different browser frame/windows)
354 // This session id is used below in the restoreSession.php include to create a
355 // session cookie for this specific OpenEMR instance that is then maintained
356 // within the OpenEMR instance by calling top.restoreSession() whenever
357 // refreshing or starting a new script.
358 if (isset($_POST['new_login_session_management'])) {
359 // This is a new login, so create a new session id and remove the old session
360 session_regenerate_id(true);
361 } else {
362 // This is not a new login, so check csrf and then create a new session id and do NOT remove the old session
363 if (!verifyCsrfToken($_POST["csrf_token_form"])) {
364 csrfNotVerified();
366 session_regenerate_id(false);
368 // Create the csrf_token
369 $_SESSION['csrf_token'] = createCsrfToken();
370 // Also create a api_csrf_token that is only used for the api
371 $_SESSION['api_csrf_token'] = createCsrfToken();
373 $_SESSION["encounter"] = '';
375 if ($GLOBALS['login_into_facility']) {
376 $facility_id = $_POST['facility'];
377 if ($facility_id === 'user_default') {
378 //get the default facility of login user from users table
379 $facilityService = new FacilityService();
380 $facility = $facilityService->getFacilityForUser($_SESSION['authUserID']);
381 $facility_id = $facility['id'];
383 $_SESSION['facilityId'] = $facility_id;
384 if ($GLOBALS['set_facility_cookie']) {
385 // set cookie with facility for the calender screens
386 setcookie("pc_facility", $_SESSION['facilityId'], time() + (3600 * 365), $GLOBALS['webroot']);
390 // Fetch the password expiration date
391 $is_expired=false;
392 if ($GLOBALS['password_expiration_days'] != 0) {
393 $is_expired=false;
394 $q= (isset($_POST['authUser'])) ? $_POST['authUser'] : '';
395 $result = sqlStatement("select pwd_expiration_date from users where username = ?", array($q));
396 $current_date = date('Y-m-d');
397 $pwd_expires_date = $current_date;
398 if ($row = sqlFetchArray($result)) {
399 $pwd_expires_date = $row['pwd_expiration_date'];
402 // Display the password expiration message (starting from 7 days before the password gets expired)
403 $pwd_alert_date = date('Y-m-d', strtotime($pwd_expires_date . '-7 days'));
405 if (strtotime($pwd_alert_date) != '' &&
406 strtotime($current_date) >= strtotime($pwd_alert_date) &&
407 (!isset($_SESSION['expiration_msg'])
408 or $_SESSION['expiration_msg'] == 0)) {
409 $is_expired = true;
410 $_SESSION['expiration_msg'] = 1; // only show the expired message once
414 if ($is_expired) {
415 //display the php file containing the password expiration message.
416 $frame1url = "pwd_expires_alert.php?csrf_token_form=" . attr_url(collectCsrfToken());
417 $frame1target = "adm";
418 } elseif (!empty($_POST['patientID'])) {
419 $patientID = 0 + $_POST['patientID'];
420 if (empty($_POST['encounterID'])) {
421 // Open patient summary screen (without a specific encounter)
422 $frame1url = "../patient_file/summary/demographics.php?set_pid=" . attr_url($patientID);
423 $frame1target = "pat";
424 } else {
425 // Open patient summary screen with a specific encounter
426 $encounterID = 0 + $_POST['encounterID'];
427 $frame1url = "../patient_file/summary/demographics.php?set_pid=" . attr_url($patientID) . "&set_encounterid=" . attr_url($encounterID);
428 $frame1target = "pat";
430 } elseif (isset($_GET['mode']) && $_GET['mode'] == "loadcalendar") {
431 $frame1url = "calendar/index.php?pid=" . attr_url($_GET['pid']);
432 if (isset($_GET['date'])) {
433 $frame1url .= "&date=" . attr_url($_GET['date']);
436 $frame1target = "cal";
437 } else {
438 // standard layout
439 $map_paths_to_targets = array(
440 'main_info.php' => ('cal'),
441 '../new/new.php' => ('pat'),
442 '../../interface/main/finder/dynamic_finder.php' => ('fin'),
443 '../../interface/patient_tracker/patient_tracker.php?skip_timeout_reset=1' => ('flb'),
444 '../../interface/main/messages/messages.php?form_active=1' => ('msg')
446 if ($GLOBALS['default_top_pane']) {
447 $frame1url=attr($GLOBALS['default_top_pane']);
448 $frame1target = $map_paths_to_targets[$GLOBALS['default_top_pane']];
449 if (empty($frame1target)) {
450 $frame1target = "msc";
452 } else {
453 $frame1url = "main_info.php";
454 $frame1target = "cal";
456 if ($GLOBALS['default_second_tab']) {
457 $frame2url=attr($GLOBALS['default_second_tab']);
458 $frame2target = $map_paths_to_targets[$GLOBALS['default_second_tab']];
459 if (empty($frame2target)) {
460 $frame2target = "msc";
462 } else {
463 $frame2url = "../../interface/main/messages/messages.php?form_active=1";
464 $frame2target = "msg";
468 $nav_area_width = '130';
469 if (!empty($GLOBALS['gbl_nav_area_width'])) {
470 $nav_area_width = $GLOBALS['gbl_nav_area_width'];
473 // This is where will decide whether to use tabs layout or non-tabs layout
474 // Will also set Session variables to communicate settings to tab layout
475 if ($GLOBALS['new_tabs_layout']) {
476 $_SESSION['frame1url'] = $frame1url;
477 $_SESSION['frame1target'] = $frame1target;
478 $_SESSION['frame2url'] = $frame2url;
479 $_SESSION['frame2target'] = $frame2target;
480 // mdsupport - Apps processing invoked for valid app selections from list
481 if ((isset($_POST['appChoice'])) && ($_POST['appChoice'] !== '*OpenEMR')) {
482 $_SESSION['app1'] = $_POST['appChoice'];
485 // Pass a unique token, so main.php script can not be run on its own
486 $_SESSION['token_main_php'] = RandomGenUtils::createUniqueToken();
487 header('Location: ' . $web_root . "/interface/main/tabs/main.php?token_main=" . urlencode($_SESSION['token_main_php']));
488 exit();
492 <html>
493 <head>
494 <title>
495 <?php echo text($openemr_name) ?>
496 </title>
497 <script type="text/javascript" src="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-1-9-1/jquery.min.js"></script>
498 <script type="text/javascript" src="../../library/topdialog.js"></script>
499 <script type="text/javascript" src="tabs/js/dialog_utils.js?v=<?php echo $v_js_includes; ?>"></script>
501 <link rel="shortcut icon" href="<?php echo $GLOBALS['images_static_relative']; ?>/favicon.ico" />
503 <script language='JavaScript'>
505 // Flag that tab mode is off
506 var tab_mode=false;
507 // some globals to access using top.variable
508 var userDebug = <?php echo js_escape($GLOBALS['user_debug']); ?>;
509 var webroot_url = <?php echo js_escape($web_root); ?>;
510 var jsLanguageDirection = <?php echo js_escape($_SESSION['language_direction']); ?>;
512 <?php require($GLOBALS['srcdir'] . "/restoreSession.php"); ?>
514 // Since this should be the parent window, this is to prevent calls to the
515 // window that opened this window. For example when a new window is opened
516 // from the Patient Flow Board or the Patient Finder.
517 window.opener = null;
519 // This flag indicates if another window or frame is trying to reload the login
520 // page to this top-level window. It is set by javascript returned by auth.inc
521 // and is checked by handlers of beforeunload events.
522 var timed_out = false;
524 // This counts the number of frames that have reported themselves as loaded.
525 // Currently only left_nav and Title do this, so the maximum will be 2.
526 // This is used to determine when those frames are all loaded.
527 var loadedFrameCount = 0;
529 function allFramesLoaded() {
530 // Change this number if more frames participate in reporting.
531 return loadedFrameCount >= 2;
533 </script>
535 </head>
537 <?php
539 * for RTL layout we need to change order of frames in framesets
541 $lang_dir = $_SESSION['language_direction'];
543 $sidebar_tpl = "<frameset rows='*,0' frameborder='0' border='0' framespacing='0'>
544 <frame src='left_nav.php' name='left_nav' />
545 <frame src='daemon_frame.php' name='Daemon' scrolling='no' frameborder='0'
546 border='0' framespacing='0' />
547 </frameset>";
549 $main_tpl = "<frameset rows='60%,*' id='fsright' bordercolor='#999999' frameborder='1'>" ;
550 $main_tpl .= "<frame src='". $frame1url ."' name='RTop' scrolling='auto' />
551 <frame src='messages/messages.php?form_active=1' name='RBot' scrolling='auto' /></frameset>";
553 // Please keep in mind that border (mozilla) and framespacing (ie) are the
554 // same thing. use both.
555 // frameborder specifies a 3d look, not whether there are borders.
557 if (empty($GLOBALS['gbl_tall_nav_area'])) {
558 // not tall nav area ?>
559 <frameset rows='<?php echo attr($GLOBALS['titleBarHeight']) + 5 ?>,*' frameborder='1' border='1' framespacing='1' onunload='imclosing()'>
560 <frame src='main_title.php' name='Title' scrolling='no' frameborder='1' noresize />
561 <?php if ($lang_dir != 'rtl') { ?>
562 <frameset cols='<?php echo attr($nav_area_width) . ',*'; ?>' id='fsbody' frameborder='1' border='4' framespacing='4'>
563 <?php echo $sidebar_tpl ?>
564 <?php echo $main_tpl ?>
565 </frameset>
567 <?php } else { ?>
568 <frameset cols='<?php echo '*,' . attr($nav_area_width); ?>' id='fsbody' frameborder='1' border='4' framespacing='4'>
569 <?php echo $main_tpl ?>
570 <?php echo $sidebar_tpl ?>
571 </frameset>
573 <?php } ?>
575 </frameset>
576 </frameset>
578 <?php } else { // use tall nav area ?>
579 <frameset cols='<?php echo attr($nav_area_width); ?>,*' id='fsbody' frameborder='1' border='4' framespacing='4' onunload='imclosing()'>
580 <frameset rows='*,0' frameborder='0' border='0' framespacing='0'>
581 <frame src='left_nav.php' name='left_nav' />
582 <frame src='daemon_frame.php' name='Daemon' scrolling='no' frameborder='0'
583 border='0' framespacing='0' />
584 </frameset>
585 <frameset rows='<?php echo attr($GLOBALS['titleBarHeight']) + 5 ?>,*' frameborder='1' border='1' framespacing='1'>
586 <frame src='main_title.php' name='Title' scrolling='no' frameborder='1' />
587 <frameset rows='60%,*' id='fsright' bordercolor='#999999' frameborder='1' border='4' framespacing='4'>
588 <frame src='<?php echo $frame1url ?>' name='RTop' scrolling='auto' />
589 <frame src='messages/messages.php?form_active=1' name='RBot' scrolling='auto' />
590 </frameset>
591 </frameset>
592 </frameset>
594 <?php } // end tall nav area ?>
596 </html>