totp.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  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-2023
  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. //set a default template
  63. $_SESSION['domain']['template']['name'] = 'default';
  64. $_SESSION['theme']['menu_brand_image']['text'] = PROJECT_PATH.'/themes/default/images/logo.png';
  65. $_SESSION['theme']['menu_brand_type']['text'] = 'image';
  66. //get the domain
  67. $domain_array = explode(":", $_SERVER["HTTP_HOST"]);
  68. $domain_name = $domain_array[0];
  69. //temp directory
  70. $_SESSION['server']['temp']['dir'] = '/tmp';
  71. //create token
  72. //$object = new token;
  73. //$token = $object->create('login');
  74. //add multi-lingual support
  75. $language = new text;
  76. $text = $language->get(null, '/core/authentication');
  77. //initialize a template object
  78. $view = new template();
  79. $view->engine = 'smarty';
  80. $view->template_dir = $_SERVER["DOCUMENT_ROOT"].PROJECT_PATH.'/core/authentication/resources/views/';
  81. $view->cache_dir = $_SESSION['server']['temp']['dir'];
  82. $view->init();
  83. //assign default values to the template
  84. $view->assign("project_path", PROJECT_PATH);
  85. $view->assign("login_destination_url", $settings['login']['destination']);
  86. $view->assign("favicon", $settings['theme']['favicon']);
  87. $view->assign("login_title", $text['label-username']);
  88. $view->assign("login_username", $text['label-username']);
  89. $view->assign("login_logo_width", $settings['theme']['login_logo_width']);
  90. $view->assign("login_logo_height", $settings['theme']['login_logo_height']);
  91. $view->assign("login_logo_source", $settings['theme']['logo']);
  92. $view->assign("button_login", $text['button-login']);
  93. $view->assign("favicon", $settings['theme']['favicon']);
  94. $view->assign("message_delay", $settings['theme']['message_delay']);
  95. //messages
  96. $view->assign('messages', message::html(true, ' '));
  97. //show the views
  98. $content = $view->render('username.htm');
  99. echo $content;
  100. exit;
  101. }
  102. //show the authentication code view
  103. if (!isset($_POST['authentication_code'])) {
  104. //get the username
  105. if (!isset($this->username) && isset($_REQUEST['username'])) {
  106. $this->username = $_REQUEST['username'];
  107. $_SESSION['username'] = $this->username;
  108. }
  109. //get the domain name
  110. if (!empty($_SESSION['username'])) {
  111. $auth = new authentication;
  112. $auth->get_domain();
  113. $this->domain_uuid = $_SESSION['domain_uuid'];
  114. $this->domain_name = $_SESSION['domain_name'];
  115. $this->username = $_SESSION['username'];
  116. }
  117. //get the user details
  118. $sql = "select user_uuid, username, user_email, contact_uuid, user_totp_secret\n";
  119. $sql .= "from v_users\n";
  120. $sql .= "where (\n";
  121. $sql .= " username = :username\n";
  122. $sql .= " or user_email = :username\n";
  123. $sql .= ")\n";
  124. if (empty($_SESSION["users"]["unique"]["text"]) || $_SESSION["users"]["unique"]["text"] != "global") {
  125. //unique username per domain (not globally unique across system - example: email address)
  126. $sql .= "and domain_uuid = :domain_uuid ";
  127. $parameters['domain_uuid'] = $this->domain_uuid;
  128. }
  129. $sql .= "and (user_type = 'default' or user_type is null) ";
  130. $parameters['username'] = $this->username;
  131. $database = new database;
  132. $row = $database->select($sql, $parameters, 'row');
  133. if (empty($row) || !is_array($row) || @sizeof($row) == 0) {
  134. //clear submitted usernames
  135. unset($this->username, $_SESSION['username'], $_REQUEST['username'], $_POST['username']);
  136. //build the result array
  137. $result["plugin"] = "totp";
  138. $result["domain_uuid"] = $_SESSION["domain_uuid"];
  139. $result["domain_name"] = $_SESSION["domain_name"];
  140. $result["authorized"] = false;
  141. //retun the array
  142. return $result;
  143. }
  144. unset($parameters);
  145. //set class variables
  146. $this->user_uuid = $row['user_uuid'];
  147. $this->user_email = $row['user_email'];
  148. $this->contact_uuid = $row['contact_uuid'];
  149. $this->user_totp_secret = $row['user_totp_secret'];
  150. //set a few session variables
  151. $_SESSION["user_uuid"] = $row['user_uuid'];
  152. $_SESSION["username"] = $row['username'];
  153. $_SESSION["user_email"] = $row['user_email'];
  154. $_SESSION["contact_uuid"] = $row["contact_uuid"];
  155. //set a default template
  156. $_SESSION['domain']['template']['name'] = 'default';
  157. $_SESSION['theme']['menu_brand_image']['text'] = PROJECT_PATH.'/themes/default/images/logo.png';
  158. $_SESSION['theme']['menu_brand_type']['text'] = 'image';
  159. //get the domain
  160. $domain_array = explode(":", $_SERVER["HTTP_HOST"]);
  161. $domain_name = $domain_array[0];
  162. //temp directory
  163. $_SESSION['server']['temp']['dir'] = '/tmp';
  164. //create token
  165. //$object = new token;
  166. //$token = $object->create('login');
  167. //add multi-lingual support
  168. $language = new text;
  169. $text = $language->get(null, '/core/authentication');
  170. //initialize a template object
  171. $view = new template();
  172. $view->engine = 'smarty';
  173. $view->template_dir = $_SERVER["DOCUMENT_ROOT"].PROJECT_PATH.'/core/authentication/resources/views/';
  174. $view->cache_dir = $_SESSION['server']['temp']['dir'];
  175. $view->init();
  176. //assign values to the template
  177. $view->assign("project_path", PROJECT_PATH);
  178. $view->assign("login_destination_url", $settings['login']['destination']);
  179. $view->assign("favicon", $settings['theme']['favicon']);
  180. $view->assign("login_title", $text['label-verify']);
  181. $view->assign("login_totp_description", $text['label-totp_description']);
  182. $view->assign("login_authentication_code", $text['label-authentication_code']);
  183. $view->assign("login_logo_width", $settings['theme']['login_logo_width']);
  184. $view->assign("login_logo_height", $settings['theme']['login_logo_height']);
  185. $view->assign("login_logo_source", $settings['theme']['logo']);
  186. $view->assign("favicon", $settings['theme']['favicon']);
  187. $view->assign("background_video", $settings['theme']['background_video']);
  188. if (!empty($_SESSION['username'])) {
  189. $view->assign("username", $_SESSION['username']);
  190. $view->assign("button_cancel", $text['button-cancel']);
  191. }
  192. //show the views
  193. if (!empty($_SESSION['authentication']['plugin']['database']['authorized']) && empty($this->user_totp_secret)) {
  194. //create the totp secret
  195. $base32 = new base2n(5, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', FALSE, TRUE, TRUE);
  196. $user_totp_secret = $base32->encode(generate_password(20,3));
  197. $this->user_totp_secret = $user_totp_secret;
  198. //add user setting to array for update
  199. $x = 0;
  200. $array['users'][$x]['user_uuid'] = $this->user_uuid;
  201. $array['users'][$x]['domain_uuid'] = $this->domain_uuid;
  202. $array['users'][$x]['user_totp_secret'] = $this->user_totp_secret;
  203. //add the user_edit permission
  204. $p = new permissions;
  205. $p->add("user_edit", "temp");
  206. //save the data
  207. $database = new database;
  208. $database->app_name = 'users';
  209. $database->app_uuid = '112124b3-95c2-5352-7e9d-d14c0b88f207';
  210. $database->save($array);
  211. //remove the temporary permission
  212. $p->delete("user_edit", "temp");
  213. //qr code includes
  214. require_once 'resources/qr_code/QRErrorCorrectLevel.php';
  215. require_once 'resources/qr_code/QRCode.php';
  216. require_once 'resources/qr_code/QRCodeImage.php';
  217. //build the otp authentication url
  218. $otpauth = "otpauth://totp/".$this->username;
  219. $otpauth .= "?secret=".$this->user_totp_secret;
  220. $otpauth .= "&issuer=".$_SESSION['domain_name'];
  221. //build the qr code image
  222. try {
  223. $code = new QRCode (- 1, QRErrorCorrectLevel::H);
  224. $code->addData($otpauth);
  225. $code->make();
  226. $img = new QRCodeImage ($code, $width=210, $height=210, $quality=50);
  227. $img->draw();
  228. $image = $img->getImage();
  229. $img->finish();
  230. }
  231. catch (Exception $error) {
  232. echo $error;
  233. }
  234. //assign values to the template
  235. $view->assign("totp_secret", $this->user_totp_secret);
  236. $view->assign("totp_image", base64_encode($image));
  237. $view->assign("totp_description", $text['description-totp']);
  238. $view->assign("button_next", $text['button-next']);
  239. $view->assign("favicon", $settings['theme']['favicon']);
  240. $view->assign("message_delay", $settings['theme']['message_delay']);
  241. //messages
  242. $view->assign('messages', message::html(true, ' '));
  243. //render the template
  244. $content = $view->render('totp_secret.htm');
  245. }
  246. else {
  247. //assign values to the template
  248. $view->assign("button_verify", $text['label-verify']);
  249. $view->assign("message_delay", $settings['theme']['message_delay']);
  250. //messages
  251. $view->assign('messages', message::html(true, ' '));
  252. //render the template
  253. $content = $view->render('totp.htm');
  254. }
  255. echo $content;
  256. exit;
  257. }
  258. //if authorized then verify
  259. if (isset($_POST['authentication_code'])) {
  260. //get the user details
  261. $sql = "select user_uuid, user_email, contact_uuid, user_totp_secret\n";
  262. $sql .= "from v_users\n";
  263. $sql .= "where (\n";
  264. $sql .= " username = :username\n";
  265. $sql .= " or user_email = :username\n";
  266. $sql .= ")\n";
  267. if ($settings['users']['unique'] != "global") {
  268. //unique username per domain (not globally unique across system - example: email address)
  269. $sql .= "and domain_uuid = :domain_uuid ";
  270. $parameters['domain_uuid'] = $_SESSION["domain_uuid"];
  271. }
  272. $parameters['username'] = $_SESSION["username"];
  273. $database = new database;
  274. $row = $database->select($sql, $parameters, 'row');
  275. $this->user_uuid = $row['user_uuid'];
  276. $this->user_email = $row['user_email'];
  277. $this->contact_uuid = $row['contact_uuid'];
  278. $this->user_totp_secret = $row['user_totp_secret'];
  279. unset($parameters);
  280. //create the authenticator object
  281. $totp = new google_authenticator;
  282. //validate the code
  283. if ($totp->checkCode($this->user_totp_secret, $_POST['authentication_code'])) {
  284. $auth_valid = true;
  285. }
  286. else {
  287. $auth_valid = false;
  288. }
  289. //clear posted authentication code
  290. unset($_POST['authentication_code']);
  291. //get the user details
  292. if ($auth_valid) {
  293. //get user data from the database
  294. $sql = "select user_uuid, username, user_email, contact_uuid ";
  295. $sql .= "from v_users ";
  296. $sql .= "where user_uuid = :user_uuid ";
  297. if ($settings['users']['unique'] != "global") {
  298. //unique username per domain (not globally unique across system - example: email address)
  299. $sql .= "and domain_uuid = :domain_uuid ";
  300. $parameters['domain_uuid'] = $_SESSION["domain_uuid"];
  301. }
  302. $parameters['user_uuid'] = $_SESSION["user_uuid"];
  303. $database = new database;
  304. $row = $database->select($sql, $parameters, 'row');
  305. unset($parameters);
  306. }
  307. else {
  308. // //destroy session
  309. // session_unset();
  310. // session_destroy();
  311. //
  312. // //send http 403
  313. // header('HTTP/1.0 403 Forbidden', true, 403);
  314. //
  315. // //redirect to the root of the website
  316. // header("Location: ".PROJECT_PATH."/");
  317. //
  318. // //exit the code
  319. // exit();
  320. //clear authentication session
  321. unset($_SESSION['authentication']);
  322. // clear username
  323. unset($_SESSION["username"]);
  324. }
  325. /*
  326. //check if user successfully logged in during the interval
  327. //$sql = "select user_log_uuid, timestamp, user_name, user_agent, remote_address ";
  328. $sql = "select count(*) as count ";
  329. $sql .= "from v_user_logs ";
  330. $sql .= "where domain_uuid = :domain_uuid ";
  331. $sql .= "and user_uuid = :user_uuid ";
  332. $sql .= "and user_agent = :user_agent ";
  333. $sql .= "and type = 'login' ";
  334. $sql .= "and result = 'success' ";
  335. $sql .= "and floor(extract(epoch from now()) - extract(epoch from timestamp)) > 3 ";
  336. $sql .= "and floor(extract(epoch from now()) - extract(epoch from timestamp)) < 300 ";
  337. $parameters['domain_uuid'] = $this->domain_uuid;
  338. $parameters['user_uuid'] = $this->user_uuid;
  339. $parameters['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
  340. $database = new database;
  341. $user_log_count = $database->select($sql, $parameters, 'all');
  342. //view_array($user_log_count);
  343. unset($sql, $parameters);
  344. */
  345. //build the result array
  346. $result["plugin"] = "totp";
  347. $result["domain_name"] = $_SESSION["domain_name"];
  348. $result["username"] = $_SESSION["username"] ?? null;
  349. $result["user_uuid"] = $_SESSION["user_uuid"];
  350. $result["domain_uuid"] = $_SESSION["domain_uuid"];
  351. $result["contact_uuid"] = $_SESSION["contact_uuid"];
  352. $result["authorized"] = $auth_valid ? true : false;
  353. //add the failed login to user logs
  354. if (!$auth_valid) {
  355. user_logs::add($result);
  356. }
  357. //retun the array
  358. return $result;
  359. //$_SESSION['authentication']['plugin']['totp']['plugin'] = "totp";
  360. //$_SESSION['authentication']['plugin']['totp']['domain_name'] = $_SESSION["domain_name"];
  361. //$_SESSION['authentication']['plugin']['totp']['username'] = $row['username'];
  362. //$_SESSION['authentication']['plugin']['totp']['user_uuid'] = $_SESSION["user_uuid"];
  363. //$_SESSION['authentication']['plugin']['totp']['contact_uuid'] = $_SESSION["contact_uuid"];
  364. //$_SESSION['authentication']['plugin']['totp']['domain_uuid'] = $_SESSION["domain_uuid"];
  365. //$_SESSION['authentication']['plugin']['totp']['authorized'] = $auth_valid ? true : false;
  366. }
  367. }
  368. }
  369. ?>