auth.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. <?php
  2. /*
  3. * phpMeccano v0.2.0. Web-framework written with php programming language. Core module [auth.php].
  4. * Copyright (C) 2015-2019 Alexei Muzarov
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version.
  10. *
  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. *
  16. * You should have received a copy of the GNU General Public License along
  17. * with this program; if not, write to the Free Software Foundation, Inc.,
  18. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  19. *
  20. * e-mail: azexmail@gmail.com
  21. * e-mail: azexmail@mail.ru
  22. * https://bitbucket.org/azexmail/phpmeccano
  23. */
  24. namespace core;
  25. loadPHP('extclass');
  26. interface intAuth {
  27. public function __construct(\mysqli $dbLink);
  28. public function userLogin($username, $password, $useCookie = true, $cookieTime = 'month', $log = true, $blockBrute = false, $cleanSessions = true);
  29. public function login2FA($code);
  30. public function isSession();
  31. public function userLogout();
  32. public function getSession($log = true);
  33. public function userSessions($userId);
  34. public function destroyAllSessions($userId);
  35. public function destroySession($userId, $sesId);
  36. }
  37. class Auth extends ServiceMethods implements intAuth {
  38. public function __construct(\mysqli $dbLink) {
  39. if (!session_id()) {
  40. session_start();
  41. }
  42. $this->dbLink = $dbLink;
  43. }
  44. public function userLogin($username, $password, $useCookie = true, $cookieTime = 'month', $log = true, $blockBrute = false, $cleanSessions = true) {
  45. $this->zeroizeError();
  46. if (isset($_SESSION[AUTH_USER_ID])) {
  47. $this->setError(ERROR_NOT_EXECUTED, 'userLogin: close the current session before starting new one');
  48. return false;
  49. }
  50. if (!pregUName($username) && !pregMail($username)) {
  51. $this->setError(ERROR_INCORRECT_DATA, 'userLogin: username can contain only letters and numbers and has length from 3 to 20 or you should use e-mail instead of username');
  52. return false;
  53. }
  54. if (!pregPassw($password)) {
  55. $this->setError(ERROR_INCORRECT_DATA, 'userLogin: password can contain only letters, numbers and common symbols and has length from 8 to 50');
  56. return false;
  57. }
  58. $curTime = time();
  59. $terms = ['hour' => $curTime+3600,
  60. 'day' => $curTime+86400,
  61. 'week' => $curTime+604800,
  62. 'two-weeks' => $curTime+1209600,
  63. 'month' => $curTime+2592000,
  64. 'half-year' => $curTime+15552000,
  65. 'year' => $curTime+31536000];
  66. if (!isset($terms[$cookieTime])) {
  67. $useCookie = false;
  68. }
  69. if (pregUName($username)) {
  70. $qResult = $this->dbLink->query("SELECT `u`.`id`, `u`.`salt`, `l`.`code`, `l`.`dir` "
  71. . "FROM `".MECCANO_TPREF."_core_userman_groups` `g` "
  72. . "JOIN `".MECCANO_TPREF."_core_userman_users` `u` "
  73. . "ON `g`.`id`=`u`.`groupid` "
  74. . "JOIN `".MECCANO_TPREF."_core_langman_languages` `l` "
  75. . "ON `u`.`langid`=`l`.`id` "
  76. . "WHERE `u`.`username`='$username' "
  77. . "AND `u`.`active`=1 "
  78. . "AND `g`.`active`=1 ;");
  79. }
  80. else {
  81. $qResult = $this->dbLink->query("SELECT `u`.`id`, `u`.`salt`, `l`.`code`, `l`.`dir` "
  82. . "FROM `".MECCANO_TPREF."_core_userman_groups` `g` "
  83. . "JOIN `".MECCANO_TPREF."_core_userman_users` `u` "
  84. . "ON `g`.`id`=`u`.`groupid` "
  85. . "JOIN `".MECCANO_TPREF."_core_langman_languages` `l` "
  86. . "ON `u`.`langid`=`l`.`id` "
  87. . "JOIN `".MECCANO_TPREF."_core_userman_userinfo` `i` "
  88. . "ON `i`.`id`=`u`.`id` "
  89. . "WHERE `i`.`email`='$username' "
  90. . "AND `u`.`active`=1 "
  91. . "AND `g`.`active`=1 ;");
  92. }
  93. if ($this->dbLink->errno) {
  94. $this->setError(ERROR_NOT_EXECUTED, 'userLogin: unable to confirm username -> '.$this->dbLink->error);
  95. return false;
  96. }
  97. if (!$this->dbLink->affected_rows) {
  98. $this->setError(ERROR_NOT_FOUND, 'userLogin: invalid username or user (group) is disabled');
  99. return false;
  100. }
  101. list($userId, $salt, $lang, $direction) = $qResult->fetch_row();
  102. $passwEncoded = passwHash($password, $salt);
  103. // check whether password is valid
  104. if ($blockBrute) {
  105. $checkPassw = "SELECT `u`.`username`, `p`.`id`, `p`.`limited`, `p`.`doubleauth` "
  106. . "FROM `".MECCANO_TPREF."_core_userman_users` `u` "
  107. . "JOIN `".MECCANO_TPREF."_core_userman_userpass` `p` "
  108. . "ON `u`.`id`=`p`.`userid` "
  109. . "JOIN `".MECCANO_TPREF."_core_userman_temp_block` `b` "
  110. . "ON `u`.`id`=`b`.`id` "
  111. . "WHERE `u`.`id`=$userId "
  112. . "AND `p`.`password`='$passwEncoded' "
  113. . "AND `b`.`tempblock` < CURRENT_TIMESTAMP ;";
  114. }
  115. else {
  116. $checkPassw = "SELECT `u`.`username`, `p`.`id`, `p`.`limited`, `p`.`doubleauth` "
  117. . "FROM `".MECCANO_TPREF."_core_userman_users` `u` "
  118. . "JOIN `".MECCANO_TPREF."_core_userman_userpass` `p` "
  119. . "ON `u`.`id`=`p`.`userid` "
  120. . "WHERE `u`.`id`=$userId "
  121. . "AND `p`.`password`='$passwEncoded' ;";
  122. }
  123. $qResult = $this->dbLink->query($checkPassw);
  124. if ($this->dbLink->errno) {
  125. $this->setError(ERROR_NOT_EXECUTED, 'userLogin: unable to confirm password -> '.$this->dbLink->error);
  126. return false;
  127. }
  128. if (!$this->dbLink->affected_rows) {
  129. if ($blockBrute) {
  130. // check whether authentication is not blocked
  131. $qResult = $this->dbLink->query("SELECT `b`.`counter`, TO_SECONDS(`b`.`tempblock`), TO_SECONDS(CURRENT_TIMESTAMP) "
  132. . "FROM `".MECCANO_TPREF."_core_userman_temp_block` `b` "
  133. . "JOIN `".MECCANO_TPREF."_core_userman_users` `u`"
  134. . "ON `b`.`id`=`u`.`id` "
  135. . "WHERE `u`.`id`=$userId "
  136. . "AND `b`.`tempblock` < CURRENT_TIMESTAMP ;");
  137. // authentication is blocked
  138. if (!$this->dbLink->affected_rows) {
  139. $this->setError(ERROR_RESTRICTED_ACCESS, 'userLogin: user authentication is blocked temporarily');
  140. return false;
  141. }
  142. else {
  143. list($counter, $tempblock, $now) = $qResult->fetch_row();
  144. $blockperiod = strtotime(MECCANO_AUTH_BLOCK_PERIOD) - strtotime('TODAY');
  145. $difference = $now - $tempblock;
  146. // reset counter if incorrect password has not been put more than MECCANO_AUTH_BLOCK_PERIOD
  147. if ($difference > $blockperiod) {
  148. $counter = 1;
  149. }
  150. // block authentication
  151. if ($counter == MECCANO_AUTH_LIMIT) {
  152. $this->dbLink->query("UPDATE `".MECCANO_TPREF."_core_userman_temp_block` "
  153. . "SET `tempblock`=ADDTIME(CURRENT_TIMESTAMP, '".MECCANO_AUTH_BLOCK_PERIOD."'), "
  154. . "`counter`=1 "
  155. . "WHERE `id`=$userId;");
  156. if ($this->dbLink->errno) {
  157. $this->setError(ERROR_NOT_EXECUTED, 'userLogin: unable to block user authentication -> '.$this->dbLink->error);
  158. return false;
  159. }
  160. $this->setError(ERROR_RESTRICTED_ACCESS, 'userLogin: user authentication is blocked temporarily');
  161. return false;
  162. }
  163. // raise counter
  164. else {
  165. $counter += 1;
  166. $this->dbLink->query("UPDATE `".MECCANO_TPREF."_core_userman_temp_block` "
  167. . "SET `tempblock`=SUBTIME(CURRENT_TIMESTAMP, '00:00:01'), "
  168. . "`counter`=$counter "
  169. . "WHERE `id`=$userId;");
  170. if ($this->dbLink->errno) {
  171. $this->setError(ERROR_NOT_EXECUTED, 'userLogin: unable to block user authentication -> '.$this->dbLink->error);
  172. return false;
  173. }
  174. }
  175. }
  176. }
  177. $this->setError(ERROR_INCORRECT_DATA, 'userLogin: invalid password');
  178. return false;
  179. }
  180. $this->dbLink->query("UPDATE `".MECCANO_TPREF."_core_userman_temp_block` "
  181. . "SET `counter`=1 "
  182. . "WHERE `id`=$userId;");
  183. if ($this->dbLink->errno) {
  184. $this->setError(ERROR_NOT_EXECUTED, 'userLogin: unable to reset blocking counter -> '.$this->dbLink->error);
  185. return false;
  186. }
  187. list($username, $passId, $limited, $doubleauth) = $qResult->fetch_row();
  188. // new unique session id
  189. $usi = guid();
  190. // IP and user-agent of the user
  191. $ipAddress = $_SERVER['REMOTE_ADDR'];
  192. $userAgent = $_SERVER['HTTP_USER_AGENT'];
  193. if (!$useCookie) {
  194. $term = $curTime;
  195. }
  196. else {
  197. $term = $terms[$cookieTime];
  198. setcookie(COOKIE_UNIQUE_SESSION_ID, $usi, $term, '/');
  199. }
  200. // record data about the session term
  201. $sql = [
  202. "INSERT INTO `".MECCANO_TPREF."_core_auth_usi` (`id`, `pid`, `endtime`) "
  203. . "VALUES('$usi', '$passId', FROM_UNIXTIME($term)) ;",
  204. "INSERT INTO `".MECCANO_TPREF."_core_auth_session_info` (`id`, `ip`, `useragent`, `created`) "
  205. . "VALUES('$usi', '$ipAddress', '$userAgent', CURRENT_TIMESTAMP) ;"
  206. ];
  207. foreach ($sql as $key => $value) {
  208. $this->dbLink->query($value);
  209. if ($this->dbLink->errno) {
  210. $this->setError(ERROR_NOT_EXECUTED, 'userLogin: unable to set unique session identifier -> '.$this->dbLink->error);
  211. return false;
  212. }
  213. }
  214. if ($cleanSessions) {
  215. // delete expired sessions of the user
  216. $sql = [
  217. "DELETE `si` FROM `".MECCANO_TPREF."_core_auth_session_info` `si` "
  218. . "JOIN `".MECCANO_TPREF."_core_auth_usi` `s` "
  219. . "ON `si`.`id`=`s`.`id` "
  220. . "JOIN `".MECCANO_TPREF."_core_userman_userpass` `p` "
  221. . "ON `s`.`pid`=`p`.`id`"
  222. . "JOIN `".MECCANO_TPREF."_core_userman_users` `u` "
  223. . "ON `p`.`userid`=`u`.`id` "
  224. . "WHERE `u`.`id`=$userId "
  225. . "AND `s`.`endtime`<NOW() ;",
  226. "DELETE `s` FROM `".MECCANO_TPREF."_core_auth_usi` `s` "
  227. . "JOIN `".MECCANO_TPREF."_core_userman_userpass` `p` "
  228. . "ON `s`.`pid`=`p`.`id`"
  229. . "JOIN `".MECCANO_TPREF."_core_userman_users` `u` "
  230. . "ON `p`.`userid`=`u`.`id` "
  231. . "WHERE `u`.`id`=$userId "
  232. . "AND `s`.`endtime`<NOW() ;"
  233. ];
  234. foreach ($sql as $key => $value) {
  235. $this->dbLink->query($value);
  236. if ($this->dbLink->errno) {
  237. $this->setError(ERROR_NOT_EXECUTED, 'userLogin: unable to delete expired sessions of the user -> '.$this->dbLink->error);
  238. return false;
  239. }
  240. }
  241. }
  242. if ($log && !$this->newLogRecord('core', 'auth_session', "name: $username; ID: $userId; IP: $ipAddress; User-agent: $userAgent")) {
  243. $this->setError(ERROR_NOT_CRITICAL, "userLogin: -> ".$this->errExp());
  244. }
  245. // if 2-factor authentication is enabled
  246. if (((int) $doubleauth)) {
  247. $code = genPassword(8, false, true, true, false, false, false);
  248. $_SESSION[AUTH_2FA_SWAP] = [
  249. $code => [
  250. AUTH_USERNAME => $username,
  251. AUTH_USER_ID => (int) $userId,
  252. AUTH_LIMITED => (int) $limited,
  253. AUTH_LANGUAGE => $lang,
  254. AUTH_LANGUAGE_DIR => $direction,
  255. AUTH_UNIQUE_SESSION_ID => $usi,
  256. AUTH_PASSWORD_ID => $passId,
  257. AUTH_IP => $ipAddress,
  258. AUTH_USER_AGENT => $userAgent,
  259. AUTH_TOKEN => makeIdent($username),
  260. ]
  261. ];
  262. return $code;
  263. }
  264. // if 2-factor authentication is disabled
  265. // record the session valiables //
  266. $_SESSION[AUTH_USERNAME] = $username;
  267. $_SESSION[AUTH_USER_ID] = (int) $userId;
  268. $_SESSION[AUTH_LIMITED] = (int) $limited;
  269. $_SESSION[AUTH_LANGUAGE] = $lang;
  270. $_SESSION[AUTH_LANGUAGE_DIR] = $direction;
  271. // control parameters
  272. $_SESSION[AUTH_UNIQUE_SESSION_ID] = $usi;
  273. $_SESSION[AUTH_PASSWORD_ID] = $passId;
  274. $_SESSION[AUTH_IP] = $ipAddress;
  275. $_SESSION[AUTH_USER_AGENT] = $userAgent;
  276. $_SESSION[AUTH_TOKEN] = makeIdent($username);
  277. return true;
  278. }
  279. public function login2FA($code) {
  280. $this->zeroizeError();
  281. if (is_string($code) && preg_match('/^[A-Z\d]{8}$/', $code)) {
  282. if (isset($_SESSION[AUTH_2FA_SWAP]) && isset($_SESSION[AUTH_2FA_SWAP][$code])) {
  283. // record the session valiables //
  284. $_SESSION[AUTH_USERNAME] = $_SESSION[AUTH_2FA_SWAP][$code][AUTH_USERNAME];
  285. $_SESSION[AUTH_USER_ID] = $_SESSION[AUTH_2FA_SWAP][$code][AUTH_USER_ID];
  286. $_SESSION[AUTH_LIMITED] = $_SESSION[AUTH_2FA_SWAP][$code][AUTH_LIMITED];
  287. $_SESSION[AUTH_LANGUAGE] = $_SESSION[AUTH_2FA_SWAP][$code][AUTH_LANGUAGE];
  288. $_SESSION[AUTH_LANGUAGE_DIR] = $_SESSION[AUTH_2FA_SWAP][$code][AUTH_LANGUAGE_DIR];
  289. // control parameters
  290. $_SESSION[AUTH_UNIQUE_SESSION_ID] = $_SESSION[AUTH_2FA_SWAP][$code][AUTH_UNIQUE_SESSION_ID];
  291. $_SESSION[AUTH_PASSWORD_ID] = $_SESSION[AUTH_2FA_SWAP][$code][AUTH_PASSWORD_ID];
  292. $_SESSION[AUTH_IP] = $_SESSION[AUTH_2FA_SWAP][$code][AUTH_IP];
  293. $_SESSION[AUTH_USER_AGENT] = $_SESSION[AUTH_2FA_SWAP][$code][AUTH_USER_AGENT];
  294. $_SESSION[AUTH_TOKEN] = $_SESSION[AUTH_2FA_SWAP][$code][AUTH_TOKEN];
  295. unset($_SESSION[AUTH_2FA_SWAP]);
  296. return true;
  297. }
  298. else {
  299. $this->setError(ERROR_NOT_FOUND, 'login2FA: the code is not found');
  300. return false;
  301. }
  302. }
  303. else {
  304. $this->setError(ERROR_INCORRECT_DATA, 'login2FA: incorrect code format');
  305. return false;
  306. }
  307. }
  308. public function isSession() {
  309. $this->zeroizeError();
  310. if (isset($_SESSION[AUTH_USER_ID])) {
  311. if ($_SESSION[AUTH_IP] != $_SERVER['REMOTE_ADDR'] || $_SESSION[AUTH_USER_AGENT] != $_SERVER['HTTP_USER_AGENT']) {
  312. $this->userLogout();
  313. $this->setError(ERROR_NOT_EXECUTED, 'isSession: probably the session is stolen');
  314. return false;
  315. }
  316. $qResult = $this->dbLink->query("SELECT `l`.`code`, `l`.`dir`, `u`.`username`, `g`.`groupname`, `u`.`id`, `p`.`password` "
  317. . "FROM `".MECCANO_TPREF."_core_userman_groups` `g` "
  318. . "JOIN `".MECCANO_TPREF."_core_userman_users` `u` "
  319. . "ON `g`.`id`=`u`.`groupid` "
  320. . "JOIN `".MECCANO_TPREF."_core_userman_userpass` `p` "
  321. . "ON `p`.`userid`=`u`.`id` "
  322. . "JOIN `".MECCANO_TPREF."_core_auth_usi` `s` "
  323. . "ON `s`.`pid`=`p`.`id` "
  324. . "JOIN `".MECCANO_TPREF."_core_langman_languages` `l`"
  325. . "ON `l`.`id`=`u`.`langid` "
  326. . "WHERE `u`.`id`=".$_SESSION[AUTH_USER_ID]." "
  327. . "AND `u`.`active`=1 "
  328. . "AND `g`.`active`=1 "
  329. . "AND `p`.`id`='".$_SESSION[AUTH_PASSWORD_ID]."' "
  330. . "AND `s`.`id`='".$_SESSION[AUTH_UNIQUE_SESSION_ID]."' ;");
  331. if ($this->dbLink->errno) {
  332. $this->setError(ERROR_NOT_EXECUTED, 'isSession: unable to check user availability -> '.$this->dbLink->error);
  333. return false;
  334. }
  335. if (!$this->dbLink->affected_rows) {
  336. $this->userLogout();
  337. return false;
  338. }
  339. $userData = $qResult->fetch_row();
  340. $_SESSION[AUTH_LANGUAGE] = $userData[0];
  341. $_SESSION[AUTH_LANGUAGE_DIR] = $userData[1];
  342. $_SESSION[AUTH_USERNAME] = $userData[2];
  343. return true;
  344. }
  345. return false;
  346. }
  347. public function userLogout() {
  348. $this->zeroizeError();
  349. if (isset($_SESSION[AUTH_USER_ID])) {
  350. $qResult = $this->dbLink->query("SELECT `id` "
  351. . "FROM `".MECCANO_TPREF."_core_auth_usi` "
  352. . "WHERE `id`='".$_SESSION[AUTH_UNIQUE_SESSION_ID]."' ;");
  353. if ($this->dbLink->errno) {
  354. $this->setError(ERROR_NOT_EXECUTED, 'userLogout: unable to check unique session identifier -> '.$this->dbLink->error);
  355. return false;
  356. }
  357. if ($this->dbLink->affected_rows) {
  358. $sql = [
  359. "DELETE FROM `".MECCANO_TPREF."_core_auth_session_info` "
  360. . "WHERE `id`='".$_SESSION[AUTH_UNIQUE_SESSION_ID]."' ;",
  361. "DELETE FROM `".MECCANO_TPREF."_core_auth_usi` "
  362. . "WHERE `id`='".$_SESSION[AUTH_UNIQUE_SESSION_ID]."' ;"
  363. ];
  364. foreach ($sql as $key => $value) {
  365. $this->dbLink->query($value);
  366. if ($this->dbLink->errno) {
  367. $this->setError(ERROR_NOT_EXECUTED, 'userLogout: unable to delete session identifier -> '.$this->dbLink->error);
  368. return false;
  369. }
  370. if (!$this->dbLink->affected_rows) {
  371. $this->setError(ERROR_NOT_FOUND, 'userLogout: session is not found');
  372. return false;
  373. }
  374. }
  375. }
  376. if (isset($_COOKIE)) {
  377. foreach ($_COOKIE as $key => $value) {
  378. setcookie($key, '', (time() - 3600), '/');
  379. }
  380. }
  381. session_unset(); session_destroy();
  382. return true;
  383. }
  384. return false;
  385. }
  386. public function getSession($log = true) {
  387. $this->zeroizeError();
  388. if (!isset($_SESSION[AUTH_USER_ID]) && isset($_COOKIE[AUTH_UNIQUE_SESSION_ID]) && pregGuid($_COOKIE[AUTH_UNIQUE_SESSION_ID])) {
  389. $qResult = $this->dbLink->query("SELECT `p`.`id`, `p`.`limited`, `u`.`id`, `u`.`username`, `l`.`code`, `l`.`dir` "
  390. . "FROM `".MECCANO_TPREF."_core_auth_usi` `s` "
  391. . "JOIN `".MECCANO_TPREF."_core_userman_userpass` `p` "
  392. . "ON `p`.`id`=`s`.`pid` "
  393. . "JOIN `".MECCANO_TPREF."_core_userman_users` `u` "
  394. . "ON `u`.`id`=`p`.`userid` "
  395. . "JOIN `".MECCANO_TPREF."_core_langman_languages` `l` "
  396. . "ON `u`.`langid`=`l`.`id` "
  397. . "JOIN `".MECCANO_TPREF."_core_userman_groups` `g` "
  398. . "ON `g`.`id`=`u`.`groupid` "
  399. . "WHERE `s`.`id`='".$_COOKIE[AUTH_UNIQUE_SESSION_ID]."' "
  400. . "AND `s`.`endtime`>NOW() "
  401. . "AND `u`.`active`=1 "
  402. . "AND `g`.`active`=1 ;");
  403. if ($this->dbLink->errno) {
  404. $this->setError(ERROR_NOT_EXECUTED, 'getSession: unable to get user data -> '.$this->dbLink->error);
  405. return false;
  406. }
  407. if (!$this->dbLink->affected_rows) {
  408. return false;
  409. }
  410. list($passId, $limited, $userId, $username, $lang, $direction) = $qResult->fetch_row();
  411. $ipAddress = $_SERVER['REMOTE_ADDR'];
  412. $userAgent = $_SERVER['HTTP_USER_AGENT'];
  413. if ($log && !$this->newLogRecord('core', 'auth_session', "name: $username; ID: $userId; IP: $ipAddress; User-agent: $userAgent")) {
  414. $this->setError(ERROR_NOT_CRITICAL, "getSession: -> ".$this->errExp());
  415. }
  416. $_SESSION[AUTH_USERNAME] = $username;
  417. $_SESSION[AUTH_USER_ID] = (int) $userId;
  418. $_SESSION[AUTH_LIMITED] = (int) $limited;
  419. $_SESSION[AUTH_LANGUAGE] = $lang;
  420. $_SESSION[AUTH_LANGUAGE_DIR] = $direction;
  421. // control parameters
  422. $_SESSION[AUTH_UNIQUE_SESSION_ID] = $_COOKIE[AUTH_UNIQUE_SESSION_ID];
  423. $_SESSION[AUTH_PASSWORD_ID] = $passId;
  424. $_SESSION[AUTH_IP] = $ipAddress;
  425. $_SESSION[AUTH_USER_AGENT] = $userAgent;
  426. $_SESSION[AUTH_TOKEN] = makeIdent($username);
  427. return true;
  428. }
  429. return false;
  430. }
  431. public function userSessions($userId) {
  432. $this->zeroizeError();
  433. if (!is_integer($userId) || $userId<1) {
  434. $this->setError(ERROR_INCORRECT_DATA, 'userSessions: user id must be integer and greater than zero');
  435. return false;
  436. }
  437. if (!$this->checkFuncAccess('core', 'auth_user_sessions', $userId)) {
  438. $this->setError(ERROR_RESTRICTED_ACCESS, "userSessions: restricted by the policy");
  439. return false;
  440. }
  441. // delete expired sessions of the user
  442. $sql = [
  443. "DELETE `si` FROM `".MECCANO_TPREF."_core_auth_session_info` `si` "
  444. . "JOIN `".MECCANO_TPREF."_core_auth_usi` `s` "
  445. . "ON `si`.`id`=`s`.`id` "
  446. . "JOIN `".MECCANO_TPREF."_core_userman_userpass` `p` "
  447. . "ON `s`.`pid`=`p`.`id`"
  448. . "JOIN `".MECCANO_TPREF."_core_userman_users` `u` "
  449. . "ON `p`.`userid`=`u`.`id` "
  450. . "WHERE `u`.`id`=$userId "
  451. . "AND `s`.`endtime`<NOW() ;",
  452. "DELETE `s` FROM `".MECCANO_TPREF."_core_auth_usi` `s` "
  453. . "JOIN `".MECCANO_TPREF."_core_userman_userpass` `p` "
  454. . "ON `s`.`pid`=`p`.`id`"
  455. . "JOIN `".MECCANO_TPREF."_core_userman_users` `u` "
  456. . "ON `p`.`userid`=`u`.`id` "
  457. . "WHERE `u`.`id`=$userId "
  458. . "AND `s`.`endtime`<NOW() ;"
  459. ];
  460. foreach ($sql as $key => $value) {
  461. $this->dbLink->query($value);
  462. if ($this->dbLink->errno) {
  463. $this->setError(ERROR_NOT_EXECUTED, 'userSessions: unable to delete expired sessions of the user -> '.$this->dbLink->error);
  464. return false;
  465. }
  466. }
  467. // get user sessions
  468. $qResult = $this->dbLink->query(
  469. "SELECT `si`.`id` `usi`, `si`.`ip` `ip`, `si`.`useragent` `useragent`, `si`.`created` `created`, `s`.`endtime` `endtime`, `p`.`description` `info` FROM `".MECCANO_TPREF."_core_auth_session_info` `si` "
  470. . "JOIN `".MECCANO_TPREF."_core_auth_usi` `s` "
  471. . "ON `si`.`id`=`s`.`id` "
  472. . "JOIN `".MECCANO_TPREF."_core_userman_userpass` `p` "
  473. . "ON `s`.`pid`=`p`.`id`"
  474. . "JOIN `".MECCANO_TPREF."_core_userman_users` `u` "
  475. . "ON `p`.`userid`=`u`.`id` "
  476. . "WHERE `u`.`id`=$userId "
  477. . "ORDER BY `si`.`created` DESC ;"
  478. );
  479. if ($this->dbLink->errno) {
  480. $this->setError(ERROR_NOT_EXECUTED, 'userSessions: unable to get list of the user sessions -> '.$this->dbLink->error);
  481. return false;
  482. }
  483. if ($this->outputType == 'xml') {
  484. $xml = new \DOMDocument('1.0', 'utf-8');
  485. $listNode = $xml->createElement('list');
  486. $xml->appendChild($listNode);
  487. $uidAttribute = $xml->createAttribute('uid');
  488. $uidAttribute->value = $userId;
  489. $listNode->appendChild($uidAttribute);
  490. while ($row = $qResult->fetch_row()) {
  491. $sessionNode = $xml->createElement('session');
  492. $listNode->appendChild($sessionNode);
  493. $sessionNode->appendChild($xml->createElement('usi', $row[0]));
  494. $sessionNode->appendChild($xml->createElement('ip', $row[1]));
  495. $sessionNode->appendChild($xml->createElement('useragent', $row[2]));
  496. $sessionNode->appendChild($xml->createElement('created', $row[3]));
  497. $sessionNode->appendChild($xml->createElement('endtime', $row[4]));
  498. $sessionNode->appendChild($xml->createElement('info', $row[5]));
  499. }
  500. return $xml;
  501. }
  502. else {
  503. $listNode = [];
  504. $listNode['uid'] = $userId;
  505. $listNode['sessions'] = [];
  506. while ($row = $qResult->fetch_row()) {
  507. $listNode['sessions'][] = [
  508. 'usi' => $row[0],
  509. 'ip' => $row[1],
  510. 'useragent' => $row[2],
  511. 'created' => $row[3],
  512. 'endtime' => $row[4],
  513. 'info' => $row[5]
  514. ];
  515. }
  516. if ($this->outputType == 'array') {
  517. return $listNode;
  518. }
  519. else {
  520. return json_encode($listNode);
  521. }
  522. }
  523. }
  524. public function destroyAllSessions($userId) {
  525. $this->zeroizeError();
  526. if (!is_integer($userId) || $userId<1) {
  527. $this->setError(ERROR_INCORRECT_DATA, 'destroyAllSessions: user id must be integer and greater than zero');
  528. return false;
  529. }
  530. if (!$this->checkFuncAccess('core', 'auth_destroy_sessions', $userId)) {
  531. $this->setError(ERROR_RESTRICTED_ACCESS, "destroyAllSessions: restricted by the policy");
  532. return false;
  533. }
  534. // delete expired sessions of the user
  535. $sql = [
  536. "DELETE `si` FROM `".MECCANO_TPREF."_core_auth_session_info` `si` "
  537. . "JOIN `".MECCANO_TPREF."_core_auth_usi` `s` "
  538. . "ON `si`.`id`=`s`.`id` "
  539. . "JOIN `".MECCANO_TPREF."_core_userman_userpass` `p` "
  540. . "ON `s`.`pid`=`p`.`id`"
  541. . "JOIN `".MECCANO_TPREF."_core_userman_users` `u` "
  542. . "ON `p`.`userid`=`u`.`id` "
  543. . "WHERE `u`.`id`=$userId ;",
  544. "DELETE `s` FROM `".MECCANO_TPREF."_core_auth_usi` `s` "
  545. . "JOIN `".MECCANO_TPREF."_core_userman_userpass` `p` "
  546. . "ON `s`.`pid`=`p`.`id`"
  547. . "JOIN `".MECCANO_TPREF."_core_userman_users` `u` "
  548. . "ON `p`.`userid`=`u`.`id` "
  549. . "WHERE `u`.`id`=$userId ;"
  550. ];
  551. foreach ($sql as $key => $value) {
  552. $this->dbLink->query($value);
  553. if ($this->dbLink->errno) {
  554. $this->setError(ERROR_NOT_EXECUTED, 'destroyAllSessions: unable to delete sessions of the user -> '.$this->dbLink->error);
  555. return false;
  556. }
  557. }
  558. return true;
  559. }
  560. public function destroySession($userId, $sesId) {
  561. $this->zeroizeError();
  562. if (!is_integer($userId) || $userId<1) {
  563. $this->setError(ERROR_INCORRECT_DATA, 'destroyAllSessions: user id must be integer and greater than zero');
  564. return false;
  565. }
  566. if (!pregGuid($sesId)) {
  567. $this->setError(ERROR_INCORRECT_DATA, 'destroySession: incorrect unique session identifier ');
  568. return false;
  569. }
  570. if (!$this->checkFuncAccess('core', 'auth_destroy_sessions', $userId)) {
  571. $this->setError(ERROR_RESTRICTED_ACCESS, "destroySession: restricted by the policy");
  572. return false;
  573. }
  574. // delete expired sessions of the user
  575. $sql = [
  576. "DELETE `si` FROM `".MECCANO_TPREF."_core_auth_session_info` `si` "
  577. . "JOIN `".MECCANO_TPREF."_core_auth_usi` `s` "
  578. . "ON `si`.`id`=`s`.`id` "
  579. . "JOIN `".MECCANO_TPREF."_core_userman_userpass` `p` "
  580. . "ON `s`.`pid`=`p`.`id` "
  581. . "JOIN `".MECCANO_TPREF."_core_userman_users` `u` "
  582. . "ON `p`.`userid`=`u`.`id` "
  583. . "WHERE `si`.`id` = '$sesId' "
  584. . "AND `u`.`id`=$userId ;",
  585. "DELETE `s` FROM `".MECCANO_TPREF."_core_auth_usi` `s` "
  586. . "JOIN `".MECCANO_TPREF."_core_userman_userpass` `p` "
  587. . "ON `s`.`pid`=`p`.`id` "
  588. . "JOIN `".MECCANO_TPREF."_core_userman_users` `u` "
  589. . "ON `p`.`userid`=`u`.`id` "
  590. . "WHERE `s`.`id` = '$sesId' "
  591. . "AND `u`.`id`=$userId ;"
  592. ];
  593. foreach ($sql as $key => $value) {
  594. $this->dbLink->query($value);
  595. if ($this->dbLink->errno) {
  596. $this->setError(ERROR_NOT_EXECUTED, 'destroySession: unable to delete session of the user -> '.$this->dbLink->error);
  597. return false;
  598. }
  599. if (!$this->dbLink->affected_rows) {
  600. $this->setError(ERROR_NOT_FOUND, 'destroySession: session is not found');
  601. return false;
  602. }
  603. }
  604. return true;
  605. }
  606. }