totp.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. <?php
  2. /*
  3. FusionPBX
  4. Version: MPL 1.1
  5. The contents of this file are subject to the Mozilla Public License Version
  6. 1.1 (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.mozilla.org/MPL/
  9. Software distributed under the License is distributed on an "AS IS" basis,
  10. WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11. for the specific language governing rights and limitations under the
  12. License.
  13. The Original Code is FusionPBX
  14. The Initial Developer of the Original Code is
  15. Mark J Crane <[email protected]>
  16. Portions created by the Initial Developer are Copyright (C) 2008-2024
  17. the Initial Developer. All Rights Reserved.
  18. Contributor(s):
  19. Mark J Crane <[email protected]>
  20. */
  21. /**
  22. * plugin_totp
  23. *
  24. * @method totp time based one time password authenticate the user
  25. */
  26. class plugin_totp {
  27. /**
  28. * Define variables and their scope
  29. */
  30. public $debug;
  31. public $domain_name;
  32. public $username;
  33. public $password;
  34. public $user_uuid;
  35. public $user_email;
  36. public $contact_uuid;
  37. private $user_totp_secret;
  38. /**
  39. * time based one time password aka totp
  40. * @return array [authorized] => true or false
  41. */
  42. function totp() {
  43. //pre-process some settings
  44. $settings['theme']['favicon'] = !empty($_SESSION['theme']['favicon']['text']) ? $_SESSION['theme']['favicon']['text'] : PROJECT_PATH.'/themes/default/favicon.ico';
  45. $settings['login']['destination'] = !empty($_SESSION['login']['destination']['text']) ? $_SESSION['login']['destination']['text'] : '';
  46. $settings['users']['unique'] = !empty($_SESSION['users']['unique']['text']) ? $_SESSION['users']['unique']['text'] : '';
  47. $settings['theme']['logo'] = !empty($_SESSION['theme']['logo']['text']) ? $_SESSION['theme']['logo']['text'] : PROJECT_PATH.'/themes/default/images/logo_login.png';
  48. $settings['theme']['login_logo_width'] = !empty($_SESSION['theme']['login_logo_width']['text']) ? $_SESSION['theme']['login_logo_width']['text'] : 'auto; max-width: 300px';
  49. $settings['theme']['login_logo_height'] = !empty($_SESSION['theme']['login_logo_height']['text']) ? $_SESSION['theme']['login_logo_height']['text'] : 'auto; max-height: 300px';
  50. $settings['theme']['message_delay'] = isset($_SESSION['theme']['message_delay']) ? 1000 * (float) $_SESSION['theme']['message_delay'] : 3000;
  51. $settings['theme']['background_video'] = isset($_SESSION['theme']['background_video'][0]) ? $_SESSION['theme']['background_video'][0] : null;
  52. //get the username
  53. if (isset($_SESSION["username"])) {
  54. $this->username = $_SESSION["username"];
  55. }
  56. if (isset($_POST['username'])) {
  57. $this->username = $_POST['username'];
  58. $_SESSION["username"] = $this->username;
  59. }
  60. //request the username
  61. if (!$this->username && !isset($_POST['authentication_code'])) {
  62. //get the domain
  63. $domain_array = explode(":", $_SERVER["HTTP_HOST"]);
  64. $domain_name = $domain_array[0];
  65. //create token
  66. //$object = new token;
  67. //$token = $object->create('login');
  68. //add multi-lingual support
  69. $language = new text;
  70. $text = $language->get(null, '/core/authentication');
  71. //initialize a template object
  72. $view = new template();
  73. $view->engine = 'smarty';
  74. $view->template_dir = $_SERVER["DOCUMENT_ROOT"].PROJECT_PATH.'/core/authentication/resources/views/';
  75. $view->cache_dir = sys_get_temp_dir();
  76. $view->init();
  77. //assign default values to the template
  78. $view->assign("project_path", PROJECT_PATH);
  79. $view->assign("login_destination_url", $settings['login']['destination']);
  80. $view->assign("favicon", $settings['theme']['favicon']);
  81. $view->assign("login_title", $text['label-username']);
  82. $view->assign("login_username", $text['label-username']);
  83. $view->assign("login_logo_width", $settings['theme']['login_logo_width']);
  84. $view->assign("login_logo_height", $settings['theme']['login_logo_height']);
  85. $view->assign("login_logo_source", $settings['theme']['logo']);
  86. $view->assign("button_login", $text['button-login']);
  87. $view->assign("favicon", $settings['theme']['favicon']);
  88. $view->assign("message_delay", $settings['theme']['message_delay']);
  89. //messages
  90. $view->assign('messages', message::html(true, ' '));
  91. //show the views
  92. $content = $view->render('username.htm');
  93. echo $content;
  94. exit;
  95. }
  96. //show the authentication code view
  97. if (!isset($_POST['authentication_code'])) {
  98. //get the username
  99. if (!isset($this->username) && isset($_REQUEST['username'])) {
  100. $this->username = $_REQUEST['username'];
  101. $_SESSION['username'] = $this->username;
  102. }
  103. //get the domain name
  104. if (!empty($_SESSION['username'])) {
  105. $auth = new authentication;
  106. $auth->get_domain();
  107. $this->domain_uuid = $_SESSION['domain_uuid'];
  108. $this->domain_name = $_SESSION['domain_name'];
  109. $this->username = $_SESSION['username'];
  110. }
  111. //get the user details
  112. $sql = "select user_uuid, username, user_email, contact_uuid, user_totp_secret\n";
  113. $sql .= "from v_users\n";
  114. $sql .= "where (\n";
  115. $sql .= " username = :username\n";
  116. $sql .= " or user_email = :username\n";
  117. $sql .= ")\n";
  118. if (empty($_SESSION["users"]["unique"]["text"]) || $_SESSION["users"]["unique"]["text"] != "global") {
  119. //unique username per domain (not globally unique across system - example: email address)
  120. $sql .= "and domain_uuid = :domain_uuid ";
  121. $parameters['domain_uuid'] = $this->domain_uuid;
  122. }
  123. $sql .= "and (user_type = 'default' or user_type is null) ";
  124. $parameters['username'] = $this->username;
  125. $database = new database;
  126. $row = $database->select($sql, $parameters, 'row');
  127. if (empty($row) || !is_array($row) || @sizeof($row) == 0) {
  128. //clear submitted usernames
  129. unset($this->username, $_SESSION['username'], $_REQUEST['username'], $_POST['username']);
  130. //build the result array
  131. $result["plugin"] = "totp";
  132. $result["domain_uuid"] = $_SESSION["domain_uuid"];
  133. $result["domain_name"] = $_SESSION["domain_name"];
  134. $result["authorized"] = false;
  135. //retun the array
  136. return $result;
  137. }
  138. unset($parameters);
  139. //set class variables
  140. $this->user_uuid = $row['user_uuid'];
  141. $this->user_email = $row['user_email'];
  142. $this->contact_uuid = $row['contact_uuid'];
  143. $this->user_totp_secret = $row['user_totp_secret'];
  144. //set a few session variables
  145. $_SESSION["user_uuid"] = $row['user_uuid'];
  146. $_SESSION["username"] = $row['username'];
  147. $_SESSION["user_email"] = $row['user_email'];
  148. $_SESSION["contact_uuid"] = $row["contact_uuid"];
  149. //get the domain
  150. $domain_array = explode(":", $_SERVER["HTTP_HOST"]);
  151. $domain_name = $domain_array[0];
  152. //create token
  153. //$object = new token;
  154. //$token = $object->create('login');
  155. //add multi-lingual support
  156. $language = new text;
  157. $text = $language->get(null, '/core/authentication');
  158. //initialize a template object
  159. $view = new template();
  160. $view->engine = 'smarty';
  161. $view->template_dir = $_SERVER["DOCUMENT_ROOT"].PROJECT_PATH.'/core/authentication/resources/views/';
  162. $view->cache_dir = sys_get_temp_dir();
  163. $view->init();
  164. //assign values to the template
  165. $view->assign("project_path", PROJECT_PATH);
  166. $view->assign("login_destination_url", $settings['login']['destination']);
  167. $view->assign("favicon", $settings['theme']['favicon']);
  168. $view->assign("login_title", $text['label-verify']);
  169. $view->assign("login_totp_description", $text['label-totp_description']);
  170. $view->assign("login_authentication_code", $text['label-authentication_code']);
  171. $view->assign("login_logo_width", $settings['theme']['login_logo_width']);
  172. $view->assign("login_logo_height", $settings['theme']['login_logo_height']);
  173. $view->assign("login_logo_source", $settings['theme']['logo']);
  174. $view->assign("favicon", $settings['theme']['favicon']);
  175. $view->assign("background_video", $settings['theme']['background_video']);
  176. if (!empty($_SESSION['username'])) {
  177. $view->assign("username", $_SESSION['username']);
  178. $view->assign("button_cancel", $text['button-cancel']);
  179. }
  180. //show the views
  181. if (!empty($_SESSION['authentication']['plugin']['database']['authorized']) && empty($this->user_totp_secret)) {
  182. //create the totp secret
  183. $base32 = new base2n(5, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', FALSE, TRUE, TRUE);
  184. $user_totp_secret = $base32->encode(generate_password(20,3));
  185. $this->user_totp_secret = $user_totp_secret;
  186. //add user setting to array for update
  187. $x = 0;
  188. $array['users'][$x]['user_uuid'] = $this->user_uuid;
  189. $array['users'][$x]['domain_uuid'] = $this->domain_uuid;
  190. $array['users'][$x]['user_totp_secret'] = $this->user_totp_secret;
  191. //add the user_edit permission
  192. $p = permissions::new();
  193. $p->add("user_edit", "temp");
  194. //save the data
  195. $database = new database;
  196. $database->app_name = 'users';
  197. $database->app_uuid = '112124b3-95c2-5352-7e9d-d14c0b88f207';
  198. $database->save($array);
  199. //remove the temporary permission
  200. $p->delete("user_edit", "temp");
  201. //qr code includes
  202. require_once 'resources/qr_code/QRErrorCorrectLevel.php';
  203. require_once 'resources/qr_code/QRCode.php';
  204. require_once 'resources/qr_code/QRCodeImage.php';
  205. //build the otp authentication url
  206. $otpauth = "otpauth://totp/".$this->username;
  207. $otpauth .= "?secret=".$this->user_totp_secret;
  208. $otpauth .= "&issuer=".$_SESSION['domain_name'];
  209. //build the qr code image
  210. try {
  211. $code = new QRCode (- 1, QRErrorCorrectLevel::H);
  212. $code->addData($otpauth);
  213. $code->make();
  214. $img = new QRCodeImage ($code, $width=210, $height=210, $quality=50);
  215. $img->draw();
  216. $image = $img->getImage();
  217. $img->finish();
  218. }
  219. catch (Exception $error) {
  220. echo $error;
  221. }
  222. //assign values to the template
  223. $view->assign("totp_secret", $this->user_totp_secret);
  224. $view->assign("totp_image", base64_encode($image));
  225. $view->assign("totp_description", $text['description-totp']);
  226. $view->assign("button_next", $text['button-next']);
  227. $view->assign("favicon", $settings['theme']['favicon']);
  228. $view->assign("message_delay", $settings['theme']['message_delay']);
  229. //messages
  230. $view->assign('messages', message::html(true, ' '));
  231. //render the template
  232. $content = $view->render('totp_secret.htm');
  233. }
  234. else {
  235. //assign values to the template
  236. $view->assign("button_verify", $text['label-verify']);
  237. $view->assign("message_delay", $settings['theme']['message_delay']);
  238. //messages
  239. $view->assign('messages', message::html(true, ' '));
  240. //render the template
  241. $content = $view->render('totp.htm');
  242. }
  243. echo $content;
  244. exit;
  245. }
  246. //if authorized then verify
  247. if (isset($_POST['authentication_code'])) {
  248. //get the user details
  249. $sql = "select user_uuid, user_email, contact_uuid, user_totp_secret\n";
  250. $sql .= "from v_users\n";
  251. $sql .= "where (\n";
  252. $sql .= " username = :username\n";
  253. $sql .= " or user_email = :username\n";
  254. $sql .= ")\n";
  255. if ($settings['users']['unique'] != "global") {
  256. //unique username per domain (not globally unique across system - example: email address)
  257. $sql .= "and domain_uuid = :domain_uuid ";
  258. $parameters['domain_uuid'] = $_SESSION["domain_uuid"];
  259. }
  260. $parameters['username'] = $_SESSION["username"];
  261. $database = new database;
  262. $row = $database->select($sql, $parameters, 'row');
  263. $this->user_uuid = $row['user_uuid'];
  264. $this->user_email = $row['user_email'];
  265. $this->contact_uuid = $row['contact_uuid'];
  266. $this->user_totp_secret = $row['user_totp_secret'];
  267. unset($parameters);
  268. //create the authenticator object
  269. $totp = new google_authenticator;
  270. //validate the code
  271. if ($totp->checkCode($this->user_totp_secret, $_POST['authentication_code'])) {
  272. $auth_valid = true;
  273. }
  274. else {
  275. $auth_valid = false;
  276. }
  277. //clear posted authentication code
  278. unset($_POST['authentication_code']);
  279. //check if contacts app exists
  280. $contacts_exists = file_exists($_SERVER["DOCUMENT_ROOT"].PROJECT_PATH.'/app/contacts/') ? true : false;
  281. //get the user details
  282. if ($auth_valid) {
  283. //get user data from the database
  284. $sql = "select ";
  285. $sql .= " u.user_uuid, ";
  286. $sql .= " u.username, ";
  287. $sql .= " u.user_email, ";
  288. $sql .= " u.contact_uuid ";
  289. if ($contacts_exists) {
  290. $sql .= ",";
  291. $sql .= "c.contact_organization, ";
  292. $sql .= "c.contact_name_given, ";
  293. $sql .= "c.contact_name_family, ";
  294. $sql .= "a.contact_attachment_uuid ";
  295. }
  296. $sql .= "from ";
  297. $sql .= " v_users as u ";
  298. if ($contacts_exists) {
  299. $sql .= "left join v_contacts as c on u.contact_uuid = c.contact_uuid and u.contact_uuid is not null ";
  300. $sql .= "left join v_contact_attachments as a on u.contact_uuid = a.contact_uuid and u.contact_uuid is not null and a.attachment_primary = 1 and a.attachment_filename is not null and a.attachment_content is not null ";
  301. }
  302. $sql .= "where ";
  303. $sql .= " u.user_uuid = :user_uuid ";
  304. if ($settings['users']['unique'] != "global") {
  305. //unique username per domain (not globally unique across system - example: email address)
  306. $sql .= "and u.domain_uuid = :domain_uuid ";
  307. $parameters['domain_uuid'] = $_SESSION["domain_uuid"];
  308. }
  309. $parameters['user_uuid'] = $_SESSION["user_uuid"];
  310. $database = new database;
  311. $row = $database->select($sql, $parameters, 'row');
  312. unset($parameters);
  313. }
  314. else {
  315. // //destroy session
  316. // session_unset();
  317. // session_destroy();
  318. //
  319. // //send http 403
  320. // header('HTTP/1.0 403 Forbidden', true, 403);
  321. //
  322. // //redirect to the root of the website
  323. // header("Location: ".PROJECT_PATH."/");
  324. //
  325. // //exit the code
  326. // exit();
  327. //clear authentication session
  328. unset($_SESSION['authentication']);
  329. // clear username
  330. unset($_SESSION["username"]);
  331. }
  332. /*
  333. //check if user successfully logged in during the interval
  334. //$sql = "select user_log_uuid, timestamp, user_name, user_agent, remote_address ";
  335. $sql = "select count(*) as count ";
  336. $sql .= "from v_user_logs ";
  337. $sql .= "where domain_uuid = :domain_uuid ";
  338. $sql .= "and user_uuid = :user_uuid ";
  339. $sql .= "and user_agent = :user_agent ";
  340. $sql .= "and type = 'login' ";
  341. $sql .= "and result = 'success' ";
  342. $sql .= "and floor(extract(epoch from now()) - extract(epoch from timestamp)) > 3 ";
  343. $sql .= "and floor(extract(epoch from now()) - extract(epoch from timestamp)) < 300 ";
  344. $parameters['domain_uuid'] = $this->domain_uuid;
  345. $parameters['user_uuid'] = $this->user_uuid;
  346. $parameters['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
  347. $database = new database;
  348. $user_log_count = $database->select($sql, $parameters, 'all');
  349. //view_array($user_log_count);
  350. unset($sql, $parameters);
  351. */
  352. //build the result array
  353. $result["plugin"] = "totp";
  354. $result["domain_name"] = $_SESSION["domain_name"];
  355. $result["username"] = $_SESSION["username"] ?? null;
  356. $result["user_uuid"] = $_SESSION["user_uuid"];
  357. $result["domain_uuid"] = $_SESSION["domain_uuid"];
  358. $result["contact_uuid"] = $_SESSION["contact_uuid"];
  359. if ($contacts_exists) {
  360. $result["contact_organization"] = $row["contact_organization"];
  361. $result["contact_name_given"] = $row["contact_name_given"];
  362. $result["contact_name_family"] = $row["contact_name_family"];
  363. $result["contact_image"] = $row["contact_attachment_uuid"];
  364. }
  365. $result["authorized"] = $auth_valid ? true : false;
  366. //add the failed login to user logs
  367. if (!$auth_valid) {
  368. user_logs::add($result);
  369. }
  370. //retun the array
  371. return $result;
  372. //$_SESSION['authentication']['plugin']['totp']['plugin'] = "totp";
  373. //$_SESSION['authentication']['plugin']['totp']['domain_name'] = $_SESSION["domain_name"];
  374. //$_SESSION['authentication']['plugin']['totp']['username'] = $row['username'];
  375. //$_SESSION['authentication']['plugin']['totp']['user_uuid'] = $_SESSION["user_uuid"];
  376. //$_SESSION['authentication']['plugin']['totp']['contact_uuid'] = $_SESSION["contact_uuid"];
  377. //$_SESSION['authentication']['plugin']['totp']['domain_uuid'] = $_SESSION["domain_uuid"];
  378. //$_SESSION['authentication']['plugin']['totp']['authorized'] = $auth_valid ? true : false;
  379. }
  380. }
  381. }
  382. ?>