feat: Displaying date in specimen column of lab results. (#6918)
[openemr.git] / interface / main / main_screen.php
blob9b70aed62023e7dab79066b2830c60ca91fe8896
1 <?php
3 /**
4 * The outside frame that holds all of the OpenEMR User Interface.
6 * @package OpenEMR
7 * @link http://www.open-emr.org
8 * @author Rod Roark <rod@sunsetsystems.com>
9 * @author Brady Miller <brady.g.miller@gmail.com>
10 * @author Ranganath Pathak <pathak@scrs1.org>
11 * @copyright Copyright (c) 2018 Rod Roark <rod@sunsetsystems.com>
12 * @copyright Copyright (c) 2018-2019 Brady Miller <brady.g.miller@gmail.com>
13 * @copyright Copyright (c) 2019 Ranganath Pathak <pathak@scrs1.org>
14 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
17 // Set $sessionAllowWrite to true to prevent session concurrency issues during authorization and app setup related code
18 $sessionAllowWrite = true;
19 require_once('../globals.php');
21 use OpenEMR\Common\Auth\AuthUtils;
22 use OpenEMR\Common\Crypto\CryptoGen;
23 use OpenEMR\Common\Csrf\CsrfUtils;
24 use OpenEMR\Common\Session\SessionTracker;
25 use OpenEMR\Common\Utils\RandomGenUtils;
26 use OpenEMR\Core\Header;
27 use OpenEMR\Services\FacilityService;
28 use OpenEMR\Services\ListService;
29 use u2flib_server\U2F;
31 ///////////////////////////////////////////////////////////////////////
32 // Functions to support MFA.
33 ///////////////////////////////////////////////////////////////////////
35 function posted_to_hidden($name)
37 if (isset($_POST[$name])) {
38 echo "<input type='hidden' name='" . attr($name) . "' value='" . attr($_POST[$name]) . "' />\r\n";
42 function generate_html_start()
45 <html>
46 <head>
47 <?php Header::setupHeader(); ?>
48 <title><?php echo xlt("MFA Authorization"); ?></title>
49 <style>
50 .alert-msg {
51 font-size:100%;
52 font-weight:700;
54 </style>
55 <?php
58 function generate_html_u2f()
60 global $appId;
62 <script src="<?php echo $GLOBALS['webroot'] ?>/library/js/u2f-api.js"></script>
63 <script>
64 function doAuth() {
65 var f = document.getElementById("u2fform");
66 var requests = JSON.parse(f.form_requests.value);
67 // The server's getAuthenticateData() repeats the same challenge in all requests.
68 var challenge = requests[0].challenge;
69 var registeredKeys = new Array();
70 for (var i = 0; i < requests.length; ++i) {
71 registeredKeys[i] = {"version": requests[i].version, "keyHandle": requests[i].keyHandle};
73 u2f.sign(
74 <?php echo js_escape($appId); ?>,
75 challenge,
76 registeredKeys,
77 function (data) {
78 if (data.errorCode && data.errorCode != 0) {
79 alert(<?php echo xlj("Key access failed with error"); ?> +' ' + data.errorCode);
80 return;
82 f.form_response.value = JSON.stringify(data);
83 f.submit();
89 </script>
90 <?php
92 function input_focus()
95 <script>
96 $(function () {
97 $('#totp').focus();
98 });
99 </script>
101 <?php
104 function generate_html_top()
106 echo '</head>';
107 echo '<body>';
110 function generate_html_middle()
112 posted_to_hidden('new_login_session_management');
113 posted_to_hidden('languageChoice');
114 posted_to_hidden('authUser');
115 posted_to_hidden('clearPass');
118 require_once(dirname(__FILE__) . "/../../src/Common/Session/SessionUtil.php");
119 function generate_html_end()
121 // to be safe, remove clearPass from memory now (if it is not empty yet)
122 if (!empty($_POST["clearPass"])) {
123 if (function_exists('sodium_memzero')) {
124 sodium_memzero($_POST["clearPass"]);
125 } else {
126 $_POST["clearPass"] = '';
129 echo "</div></body></html>\n";
130 OpenEMR\Common\Session\SessionUtil::coreSessionDestroy();
131 return 0;
134 if (isset($_POST['new_login_session_management'])) {
135 ///////////////////////////////////////////////////////////////////////
136 // Begin code to support U2F and APP Based TOTP logic.
137 ///////////////////////////////////////////////////////////////////////
138 $errormsg = '';
139 $regs = array(); // for mapping device handles to their names
140 $registrations = array(); // the array of stored registration objects
141 $res1 = sqlStatement(
142 "SELECT a.name, a.method, a.var1 FROM login_mfa_registrations AS a " .
143 "WHERE a.user_id = ? AND (a.method = 'TOTP' OR a.method = 'U2F') ORDER BY a.name",
144 array($_SESSION['authUserID'])
147 $registrationAttempt = false;
148 $isU2F = false;
149 $isTOTP = false;
150 while ($row1 = sqlFetchArray($res1)) {
151 $registrationAttempt = true;
152 if ($row1['method'] == 'U2F') {
153 $isU2F = true;
154 $regobj = json_decode($row1['var1']);
155 $regs[json_encode($regobj->keyHandle)] = $row1['name'];
156 $registrations[] = $regobj;
157 } else { // $row1['method'] == 'TOTP'
158 $isTOTP = true;
162 if ($registrationAttempt) {
163 $requests = '';
164 $errortype = '';
165 if ($isU2F) {
166 // There is at least one U2F key registered so we have to request or verify key data.
167 // https is required, and with a proxy the server might not see it.
168 $scheme = "https://"; // isset($_SERVER['HTTPS']) ? "https://" : "http://";
169 $appId = $scheme . $_SERVER['HTTP_HOST'];
170 $u2f = new u2flib_server\U2F($appId);
172 $userid = $_SESSION['authUserID'];
173 $form_response = empty($_POST['form_response']) ? '' : $_POST['form_response'];
174 if ($form_response) {
175 // TOTP METHOD enabled if TOTP is visible in post request
176 if (isset($_POST['totp']) && trim($_POST['totp']) != "" && $isTOTP) {
177 $errormsg = false;
179 $form_response = '';
181 $res1 = sqlQuery(
182 "SELECT a.var1 FROM login_mfa_registrations AS a WHERE a.user_id = ? AND a.method = 'TOTP'",
183 array($_SESSION['authUserID'])
185 $registrationSecret = false;
186 if (!empty($res1['var1'])) {
187 $registrationSecret = $res1['var1'];
190 // Decrypt the secret
191 // First, try standard method that uses standard key
192 $cryptoGen = new CryptoGen();
193 $secret = $cryptoGen->decryptStandard($registrationSecret);
194 if (empty($secret)) {
195 // Second, try the password hash, which was setup during install and is temporary
196 $passwordResults = privQuery(
197 "SELECT password FROM users_secure WHERE username = ?",
198 array($_POST["authUser"])
200 if (!empty($passwordResults["password"])) {
201 $secret = $cryptoGen->decryptStandard($registrationSecret, $passwordResults["password"]);
202 if (!empty($secret)) {
203 error_log("Disregard the decryption failed authentication error reported above this line; it is not an error.");
204 // Re-encrypt with the more secure standard key
205 $secretEncrypt = $cryptoGen->encryptStandard($secret);
206 privStatement(
207 "UPDATE login_mfa_registrations SET var1 = ? where user_id = ? AND method = 'TOTP'",
208 array($secretEncrypt, $userid)
214 if (!empty($secret)) {
215 $googleAuth = new Totp($secret);
216 $form_response = $googleAuth->validateCode($_POST['totp']);
219 if ($form_response) {
220 // Keep track of when challenges were last answered correctly.
221 privStatement(
222 "UPDATE users_secure SET last_challenge_response = NOW() WHERE id = ?",
223 array($_SESSION['authUserID'])
225 } else {
226 $errormsg = xl("The code you entered was not valid");
227 $errortype = "TOTP";
229 } elseif ($isU2F) { // Otherwise use U2F METHOD
230 // We have key data, check if it matches what was registered.
231 $tmprow = sqlQuery("SELECT login_work_area FROM users_secure WHERE id = ?", array($userid));
232 try {
233 $registration = $u2f->doAuthenticate(
234 json_decode($tmprow['login_work_area']), // these are the original challenge requests
235 $registrations,
236 json_decode($_POST['form_response'])
238 // Stored registration data needs to be updated because the usage count has changed.
239 // We have to use the matching registered key.
240 $strhandle = json_encode($registration->keyHandle);
241 if (isset($regs[$strhandle])) {
242 sqlStatement(
243 "UPDATE login_mfa_registrations SET `var1` = ? WHERE " .
244 "`user_id` = ? AND `method` = 'U2F' AND `name` = ?",
245 array(json_encode($registration), $userid, $regs[$strhandle])
247 } else {
248 error_log("Unexpected keyHandle returned from doAuthenticate(): '" . errorLogEscape($strhandle) . "'");
250 // Keep track of when challenges were last answered correctly.
251 sqlStatement(
252 "UPDATE users_secure SET last_challenge_response = NOW() WHERE id = ?",
253 array($_SESSION['authUserID'])
255 } catch (u2flib_server\Error $e) {
256 // Authentication failed so we will build the U2F form again.
257 $form_response = '';
258 $errormsg = xl('U2F Key Authentication error') . ": " . $e->getMessage();
259 $errortype = "U2F";
261 } else {
262 // do nothing
263 $form_response = '';
266 if (!$form_response) {
267 generate_html_start();
268 if ($isU2F) {
269 generate_html_u2f();
271 if ($isTOTP) {
272 input_focus();
274 generate_html_top();
275 if ($isTOTP) {
276 echo '<div class="container">';
277 echo '<div class="row">';
278 echo ' <div class="col-sm-12">';
279 echo ' <h2>' . xlt('TOTP Verification') . '</h2>';
280 echo ' </div>';
281 echo '</div>';
282 if ($errormsg && $errortype == "TOTP") {
283 echo '<div class="row"><div class="col-sm-12"><div class="alert alert-danger alert-msg">' . text($errormsg) . '</div></div></div>';
286 echo '<div class="row">';
287 echo ' <div class="col-sm-12">';
288 echo ' <form method="post" action="main_screen.php?auth=login&site=' . attr_url($_GET['site']) . '" target="_top" name="challenge_form" id=="challenge_form">';
289 echo ' <fieldset>';
290 echo ' <legend>' . xlt('Provide TOTP code') . '</legend>';
291 echo ' <div class="form-group">';
292 echo ' <div class="col-sm-6 offset-sm-3">';
293 echo ' <label for="totp">' . xlt('Enter the code from your authentication application on your device') . ':</label>';
294 echo ' <input type="text" name="totp" class="form-control input-lg" id="totp" maxlength="12" required>';
295 echo ' <input type="hidden" name="form_response" value="true" />';
296 generate_html_middle();
297 echo ' </div>';
298 echo ' </fieldset>';
299 echo ' <div class="form-group clearfix">';
300 echo ' <div class="col-sm-12 text-left position-override">';
301 echo ' <button type="submit" class="btn btn-secondary btn-save">' . xlt('Authenticate TOTP') . '</button>';
302 echo ' </div>';
303 echo ' </div>';
304 echo ' </div>';
305 echo ' </form>';
306 echo ' </div>';
307 echo '</div>';
309 if ($isU2F) {
310 // There is no key data yet or authentication failed, so we need to solicit it.
311 $requests = json_encode($u2f->getAuthenticateData($registrations));
312 // Persist the challenge also in the database because the browser is untrusted.
313 sqlStatement(
314 "UPDATE users_secure SET login_work_area = ? WHERE id = ?",
315 array($requests, $userid)
318 echo '<div class="container">';
319 echo '<div class="row">';
320 echo ' <div class="col-sm-12">';
321 echo ' <h2>' . xlt('U2F Key Verification') . '</h2>';
322 echo ' </div>';
323 echo '</div>';
324 if ($errormsg && $errortype == "U2F") {
325 echo '<div class="row"><div class="col-sm-12"><div class="alert alert-danger alert-msg">' . text($errormsg) . '</div></div></div>';
327 echo '<div class="row">';
328 echo ' <div class="col-sm-12">';
329 echo ' <form method="post" name="u2fform" id="u2fform" action="main_screen.php?auth=login&site=' . attr_url($_GET['site']) . '" target="_top">';
330 echo ' <fieldset>';
331 echo ' <legend>' . xlt('Insert U2F Key') . '</legend>';
332 echo ' <div class="form-group">';
333 echo ' <div class="col-sm-6 offset-sm-3">';
334 echo ' <ul>';
335 echo ' <li>' . xlt('Insert your key into a USB port and click the Authenticate button below.') . '</li>';
336 echo ' <li>' . xlt('Then press the flashing button on your key within 1 minute.') . '</li>';
337 echo ' </ul>';
338 echo ' </div>';
339 echo ' </fieldset>';
340 echo ' <div class="form-group clearfix">';
341 echo ' <div class="col-sm-12 text-left position-override">';
342 echo ' <button type="button" id="authutf" class="btn btn-secondary btn-save" onclick="doAuth()">' . xlt('Authenticate U2F') . '</button>';
343 echo ' <input type="hidden" name="form_requests" value="' . attr($requests) . '" />';
344 echo ' <input type="hidden" name="form_response" value="" />';
345 generate_html_middle();
346 echo ' </div>';
347 echo ' </div>';
348 echo ' </form>';
349 echo ' </div>';
350 echo '</div>';
352 exit(generate_html_end());
355 ///////////////////////////////////////////////////////////////////////
356 // End of U2F and APP Based TOTP logic.
357 ///////////////////////////////////////////////////////////////////////
360 // Creates a new session id when load this outer frame
361 // (allows creations of separate OpenEMR frames to view patients concurrently
362 // on different browser frame/windows)
363 // This session id is used below in the restoreSession.php include to create a
364 // session cookie for this specific OpenEMR instance that is then maintained
365 // within the OpenEMR instance by calling top.restoreSession() whenever
366 // refreshing or starting a new script.
368 // This is a new login, so create a new session id and remove the old session
369 session_regenerate_id(true);
370 // Also need to delete clearPass from memory
371 if (function_exists('sodium_memzero')) {
372 sodium_memzero($_POST["clearPass"]);
373 } else {
374 $_POST["clearPass"] = '';
376 } else {
377 // This is not a new login, so check csrf and then create a new session id and do NOT remove the old session
378 if (!CsrfUtils::verifyCsrfToken($_POST["csrf_token_form"])) {
379 CsrfUtils::csrfNotVerified();
381 session_regenerate_id(false);
383 // Set up the csrf private_key
384 // Note this key always remains private and never leaves server session. It is used to create
385 // the csrf tokens.
386 CsrfUtils::setupCsrfKey();
387 // Set up the session uuid. This will be used for mapping session setting to database.
388 // At this time only used for lastupdate tracking
389 SessionTracker::setupSessionDatabaseTracker();
391 $_SESSION["encounter"] = '';
393 if ($GLOBALS['login_into_facility']) {
394 $facility_id = $_POST['facility'];
395 if ($facility_id === 'user_default') {
396 //get the default facility of login user from users table
397 $facilityService = new FacilityService();
398 $facility = $facilityService->getFacilityForUser($_SESSION['authUserID']);
399 $facility_id = $facility['id'];
401 $_SESSION['facilityId'] = $facility_id;
402 if ($GLOBALS['set_facility_cookie']) {
403 // set cookie with facility for the calender screens
404 setcookie("pc_facility", $_SESSION['facilityId'], time() + (3600 * 365), $GLOBALS['webroot']);
408 // Fetch the password expiration date (note LDAP skips this)
409 $is_expired = false;
410 if ((!AuthUtils::useActiveDirectory()) && ($GLOBALS['password_expiration_days'] != 0) && (check_integer($GLOBALS['password_expiration_days']))) {
411 $result = privQuery("select `last_update_password` from `users_secure` where `id` = ?", [$_SESSION['authUserID']]);
412 $current_date = date('Y-m-d');
413 if (!empty($result['last_update_password'])) {
414 $pwd_last_update = $result['last_update_password'];
415 } else {
416 error_log("OpenEMR ERROR: there is a problem with recording of last_update_password entry in users_secure table");
417 $pwd_last_update = $current_date;
420 // Display the password expiration message (will show during the grace time)
421 $pwd_alert_date = date('Y-m-d', strtotime($pwd_last_update . '+' . $GLOBALS['password_expiration_days'] . ' days'));
423 if (empty(strtotime($pwd_alert_date))) {
424 error_log("OpenEMR ERROR: there is a problem when trying to check if user's password is expired");
425 } elseif (strtotime($current_date) >= strtotime($pwd_alert_date)) {
426 $is_expired = true;
430 $listSvc = new ListService();
431 $_tabs = $listSvc->getOptionsByListName('default_open_tabs', ['activity' => 1]);
433 if ($is_expired) {
434 //display the php file containing the password expiration message.
435 array_unshift($_tabs, [
436 'notes' => "pwd_expires_alert.php?csrf_token_form=" . attr_url(CsrfUtils::collectCsrfToken()),
437 'id' => "adm",
438 "label" => xl("Password Reset"),
440 } elseif (!empty($_POST['patientID'])) {
441 // Patient is open, so add this to the list of tabs, at the end
442 $patientID = (int) $_POST['patientID'];
443 $_notes = "../patient_file/summary/demographics.php?set_pid=" . attr_url($patientID);
444 if (!empty($_POST['encounterID'])) {
445 $encounterID = (int) $_POST['encounterID'];
446 $_notes = $_notes . "&set_encounterid=" . attr_url($encounterID);
448 $_tabs[] = [
449 'notes' => $_notes,
450 'id' => "pat",
451 'label' => xl("Dashboard"),
453 } elseif (isset($_GET['mode']) && $_GET['mode'] == "loadcalendar") {
454 // Load the calendar, at the end
455 $_notes = "calendar/index.php?pid=" . attr_url($_GET['pid']);
456 $_notes = (isset($_GET['date'])) ? $_notes . "&date=" . attr_url($_GET['date']) : $_notes;
457 $_tabs[] = [
458 'notes' => $_notes,
459 'id' => "cal",
460 "label" => xl("Calendar"),
464 // Will set Session variables to communicate settings to tab layout
465 $_SESSION['default_open_tabs'] = $_tabs;
466 // mdsupport - Apps processing invoked for valid app selections from list
467 if ((isset($_POST['appChoice'])) && ($_POST['appChoice'] !== '*OpenEMR')) {
468 $_SESSION['app1'] = $_POST['appChoice'];
471 // Pass a unique token, so main.php script can not be run on its own
472 $_SESSION['token_main_php'] = RandomGenUtils::createUniqueToken();
473 header('Location: ' . $web_root . "/interface/main/tabs/main.php?token_main=" . urlencode($_SESSION['token_main_php']));
474 exit();