PageRenderTime 771ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/application/libraries/Tank_auth.php

https://github.com/domjwdavis/XTA2
PHP | 647 lines | 367 code | 89 blank | 191 comment | 58 complexity | a42cf1fdd29646e663409803d1bf63f9 MD5 | raw file
  1. <?php if (!defined('BASEPATH')) exit('No direct script access allowed');
  2. require_once('phpass-0.3/PasswordHash.php');
  3. define('STATUS_ACTIVATED', '1');
  4. define('STATUS_NOT_ACTIVATED', '0');
  5. /**
  6. * Tank_auth
  7. *
  8. * Authentication library for Code Igniter.
  9. *
  10. * @package Tank_auth
  11. * @author Ilya Konyukhov (http://konyukhov.com/soft/)
  12. * @version 1.0.9
  13. * @based on DX Auth by Dexcell (http://dexcell.shinsengumiteam.com/dx_auth)
  14. * @license MIT License Copyright (c) 2008 Erick Hartanto
  15. */
  16. class Tank_auth
  17. {
  18. private $error = array();
  19. function __construct()
  20. {
  21. $this->ci =& get_instance();
  22. $this->ci->load->config('tank_auth', TRUE);
  23. $this->ci->load->library('session');
  24. $this->ci->load->database();
  25. $this->ci->load->model('tank_auth/users');
  26. // Try to autologin
  27. $this->autologin();
  28. }
  29. /**
  30. * Login user on the site. Return TRUE if login is successful
  31. * (user exists and activated, password is correct), otherwise FALSE.
  32. *
  33. * @param string (username or email or both depending on settings in config file)
  34. * @param string
  35. * @param bool
  36. * @return bool
  37. */
  38. function login($login, $password, $remember, $login_by_username, $login_by_email)
  39. {
  40. if ((strlen($login) > 0) AND (strlen($password) > 0)) {
  41. // Which function to use to login (based on config)
  42. if ($login_by_username AND $login_by_email) {
  43. $get_user_func = 'get_user_by_login';
  44. } else if ($login_by_username) {
  45. $get_user_func = 'get_user_by_username';
  46. } else {
  47. $get_user_func = 'get_user_by_email';
  48. }
  49. if (!is_null($user = $this->ci->users->$get_user_func($login))) { // login ok
  50. // Does password match hash in database?
  51. $hasher = new PasswordHash(
  52. $this->ci->config->item('phpass_hash_strength', 'tank_auth'),
  53. $this->ci->config->item('phpass_hash_portable', 'tank_auth'));
  54. if ($hasher->CheckPassword($password, $user->password)) { // password ok
  55. if ($user->banned == 1) { // fail - banned
  56. $this->error = array('banned' => $user->ban_reason);
  57. } else {
  58. $this->ci->session->set_userdata(array(
  59. 'user_id' => $user->id,
  60. 'username' => $user->username,
  61. 'status' => ($user->activated == 1) ? STATUS_ACTIVATED : STATUS_NOT_ACTIVATED,
  62. ));
  63. if ($user->activated == 0) { // fail - not activated
  64. $this->error = array('not_activated' => '');
  65. } else { // success
  66. if ($remember) {
  67. $this->create_autologin($user->id);
  68. }
  69. $this->clear_login_attempts($login);
  70. $this->ci->users->update_login_info(
  71. $user->id,
  72. $this->ci->config->item('login_record_ip', 'tank_auth'),
  73. $this->ci->config->item('login_record_time', 'tank_auth'));
  74. return TRUE;
  75. }
  76. }
  77. } else { // fail - wrong password
  78. $this->increase_login_attempt($login);
  79. $this->error = array('password' => 'auth_incorrect_password');
  80. }
  81. } else { // fail - wrong login
  82. $this->increase_login_attempt($login);
  83. $this->error = array('login' => 'auth_incorrect_login');
  84. }
  85. }
  86. return FALSE;
  87. }
  88. /**
  89. * Logout user from the site
  90. *
  91. * @return void
  92. */
  93. function logout()
  94. {
  95. $this->delete_autologin();
  96. // See http://codeigniter.com/forums/viewreply/662369/ as the reason for the next line
  97. $this->ci->session->set_userdata(array('user_id' => '', 'username' => '', 'status' => ''));
  98. $this->ci->tweet->logout();
  99. $this->ci->session->sess_destroy();
  100. }
  101. /**
  102. * Check if user logged in. Also test if user is activated or not.
  103. *
  104. * @param bool
  105. * @return bool
  106. */
  107. function is_logged_in($activated = TRUE)
  108. {
  109. return $this->ci->session->userdata('status') === ($activated ? STATUS_ACTIVATED : STATUS_NOT_ACTIVATED);
  110. }
  111. /**
  112. * Get user_id
  113. *
  114. * @return string
  115. */
  116. function get_user_id()
  117. {
  118. return $this->ci->session->userdata('user_id');
  119. }
  120. /**
  121. * Get username
  122. *
  123. * @return string
  124. */
  125. function get_username()
  126. {
  127. return $this->ci->session->userdata('username');
  128. }
  129. /**
  130. * Create new user on the site and return some data about it:
  131. * user_id, username, password, email, new_email_key (if any).
  132. *
  133. * @param string
  134. * @param string
  135. * @param string
  136. * @param bool
  137. * @return array
  138. */
  139. function create_user($username, $email, $password, $email_activation)
  140. {
  141. if ((strlen($username) > 0) AND !$this->ci->users->is_username_available($username)) {
  142. $this->error = array('username' => 'auth_username_in_use');
  143. } elseif (!$this->ci->users->is_email_available($email)) {
  144. $this->error = array('email' => 'auth_email_in_use');
  145. } else {
  146. // Hash password using phpass
  147. $hasher = new PasswordHash(
  148. $this->ci->config->item('phpass_hash_strength', 'tank_auth'),
  149. $this->ci->config->item('phpass_hash_portable', 'tank_auth'));
  150. $hashed_password = $hasher->HashPassword($password);
  151. $data = array(
  152. 'username' => $username,
  153. 'password' => $hashed_password,
  154. 'email' => $email,
  155. 'last_ip' => $this->ci->input->ip_address(),
  156. );
  157. if ($email_activation) {
  158. $data['new_email_key'] = md5(rand().microtime());
  159. }
  160. if (!is_null($res = $this->ci->users->create_user($data, !$email_activation))) {
  161. $data['user_id'] = $res['user_id'];
  162. $data['password'] = $password;
  163. unset($data['last_ip']);
  164. return $data;
  165. }
  166. }
  167. return NULL;
  168. }
  169. /**
  170. * Check if username available for registering.
  171. * Can be called for instant form validation.
  172. *
  173. * @param string
  174. * @return bool
  175. */
  176. function is_username_available($username)
  177. {
  178. return ((strlen($username) > 0) AND $this->ci->users->is_username_available($username));
  179. }
  180. /**
  181. * Check if email available for registering.
  182. * Can be called for instant form validation.
  183. *
  184. * @param string
  185. * @return bool
  186. */
  187. function is_email_available($email)
  188. {
  189. return ((strlen($email) > 0) AND $this->ci->users->is_email_available($email));
  190. }
  191. /**
  192. * Change email for activation and return some data about user:
  193. * user_id, username, email, new_email_key.
  194. * Can be called for not activated users only.
  195. *
  196. * @param string
  197. * @return array
  198. */
  199. function change_email($email)
  200. {
  201. $user_id = $this->ci->session->userdata('user_id');
  202. if (!is_null($user = $this->ci->users->get_user_by_id($user_id, FALSE))) {
  203. $data = array(
  204. 'user_id' => $user_id,
  205. 'username' => $user->username,
  206. 'email' => $email,
  207. );
  208. if (strtolower($user->email) == strtolower($email)) { // leave activation key as is
  209. $data['new_email_key'] = $user->new_email_key;
  210. return $data;
  211. } elseif ($this->ci->users->is_email_available($email)) {
  212. $data['new_email_key'] = md5(rand().microtime());
  213. $this->ci->users->set_new_email($user_id, $email, $data['new_email_key'], FALSE);
  214. return $data;
  215. } else {
  216. $this->error = array('email' => 'auth_email_in_use');
  217. }
  218. }
  219. return NULL;
  220. }
  221. /**
  222. * Activate user using given key
  223. *
  224. * @param string
  225. * @param string
  226. * @param bool
  227. * @return bool
  228. */
  229. function activate_user($user_id, $activation_key, $activate_by_email = TRUE)
  230. {
  231. $this->ci->users->purge_na($this->ci->config->item('email_activation_expire', 'tank_auth'));
  232. if ((strlen($user_id) > 0) AND (strlen($activation_key) > 0)) {
  233. return $this->ci->users->activate_user($user_id, $activation_key, $activate_by_email);
  234. }
  235. return FALSE;
  236. }
  237. /**
  238. * Set new password key for user and return some data about user:
  239. * user_id, username, email, new_pass_key.
  240. * The password key can be used to verify user when resetting his/her password.
  241. *
  242. * @param string
  243. * @return array
  244. */
  245. function forgot_password($login)
  246. {
  247. if (strlen($login) > 0) {
  248. if (!is_null($user = $this->ci->users->get_user_by_login($login))) {
  249. $data = array(
  250. 'user_id' => $user->id,
  251. 'username' => $user->username,
  252. 'email' => $user->email,
  253. 'new_pass_key' => md5(rand().microtime()),
  254. );
  255. $this->ci->users->set_password_key($user->id, $data['new_pass_key']);
  256. return $data;
  257. } else {
  258. $this->error = array('login' => 'auth_incorrect_email_or_username');
  259. }
  260. }
  261. return NULL;
  262. }
  263. /**
  264. * Check if given password key is valid and user is authenticated.
  265. *
  266. * @param string
  267. * @param string
  268. * @return bool
  269. */
  270. function can_reset_password($user_id, $new_pass_key)
  271. {
  272. if ((strlen($user_id) > 0) AND (strlen($new_pass_key) > 0)) {
  273. return $this->ci->users->can_reset_password(
  274. $user_id,
  275. $new_pass_key,
  276. $this->ci->config->item('forgot_password_expire', 'tank_auth'));
  277. }
  278. return FALSE;
  279. }
  280. /**
  281. * Replace user password (forgotten) with a new one (set by user)
  282. * and return some data about it: user_id, username, new_password, email.
  283. *
  284. * @param string
  285. * @param string
  286. * @return bool
  287. */
  288. function reset_password($user_id, $new_pass_key, $new_password)
  289. {
  290. if ((strlen($user_id) > 0) AND (strlen($new_pass_key) > 0) AND (strlen($new_password) > 0)) {
  291. if (!is_null($user = $this->ci->users->get_user_by_id($user_id, TRUE))) {
  292. // Hash password using phpass
  293. $hasher = new PasswordHash(
  294. $this->ci->config->item('phpass_hash_strength', 'tank_auth'),
  295. $this->ci->config->item('phpass_hash_portable', 'tank_auth'));
  296. $hashed_password = $hasher->HashPassword($new_password);
  297. if ($this->ci->users->reset_password(
  298. $user_id,
  299. $hashed_password,
  300. $new_pass_key,
  301. $this->ci->config->item('forgot_password_expire', 'tank_auth'))) { // success
  302. // Clear all user's autologins
  303. $this->ci->load->model('tank_auth/user_autologin');
  304. $this->ci->user_autologin->clear($user->id);
  305. return array(
  306. 'user_id' => $user_id,
  307. 'username' => $user->username,
  308. 'email' => $user->email,
  309. 'new_password' => $new_password,
  310. );
  311. }
  312. }
  313. }
  314. return NULL;
  315. }
  316. /**
  317. * Change user password (only when user is logged in)
  318. *
  319. * @param string
  320. * @param string
  321. * @return bool
  322. */
  323. function change_password($old_pass, $new_pass)
  324. {
  325. $user_id = $this->ci->session->userdata('user_id');
  326. if (!is_null($user = $this->ci->users->get_user_by_id($user_id, TRUE))) {
  327. // Check if old password correct
  328. $hasher = new PasswordHash(
  329. $this->ci->config->item('phpass_hash_strength', 'tank_auth'),
  330. $this->ci->config->item('phpass_hash_portable', 'tank_auth'));
  331. if ($hasher->CheckPassword($old_pass, $user->password)) { // success
  332. // Hash new password using phpass
  333. $hashed_password = $hasher->HashPassword($new_pass);
  334. // Replace old password with new one
  335. $this->ci->users->change_password($user_id, $hashed_password);
  336. return TRUE;
  337. } else { // fail
  338. $this->error = array('old_password' => 'auth_incorrect_password');
  339. }
  340. }
  341. return FALSE;
  342. }
  343. /**
  344. * Change user email (only when user is logged in) and return some data about user:
  345. * user_id, username, new_email, new_email_key.
  346. * The new email cannot be used for login or notification before it is activated.
  347. *
  348. * @param string
  349. * @param string
  350. * @return array
  351. */
  352. function set_new_email($new_email, $password)
  353. {
  354. $user_id = $this->ci->session->userdata('user_id');
  355. if (!is_null($user = $this->ci->users->get_user_by_id($user_id, TRUE))) {
  356. // Check if password correct
  357. $hasher = new PasswordHash(
  358. $this->ci->config->item('phpass_hash_strength', 'tank_auth'),
  359. $this->ci->config->item('phpass_hash_portable', 'tank_auth'));
  360. if ($hasher->CheckPassword($password, $user->password)) { // success
  361. $data = array(
  362. 'user_id' => $user_id,
  363. 'username' => $user->username,
  364. 'new_email' => $new_email,
  365. );
  366. if ($user->email == $new_email) {
  367. $this->error = array('email' => 'auth_current_email');
  368. } elseif ($user->new_email == $new_email) { // leave email key as is
  369. $data['new_email_key'] = $user->new_email_key;
  370. return $data;
  371. } elseif ($this->ci->users->is_email_available($new_email)) {
  372. $data['new_email_key'] = md5(rand().microtime());
  373. $this->ci->users->set_new_email($user_id, $new_email, $data['new_email_key'], TRUE);
  374. return $data;
  375. } else {
  376. $this->error = array('email' => 'auth_email_in_use');
  377. }
  378. } else { // fail
  379. $this->error = array('password' => 'auth_incorrect_password');
  380. }
  381. }
  382. return NULL;
  383. }
  384. /**
  385. * Activate new email, if email activation key is valid.
  386. *
  387. * @param string
  388. * @param string
  389. * @return bool
  390. */
  391. function activate_new_email($user_id, $new_email_key)
  392. {
  393. if ((strlen($user_id) > 0) AND (strlen($new_email_key) > 0)) {
  394. return $this->ci->users->activate_new_email(
  395. $user_id,
  396. $new_email_key);
  397. }
  398. return FALSE;
  399. }
  400. /**
  401. * Delete user from the site (only when user is logged in)
  402. *
  403. * @param string
  404. * @return bool
  405. */
  406. function delete_user($password)
  407. {
  408. $user_id = $this->ci->session->userdata('user_id');
  409. if (!is_null($user = $this->ci->users->get_user_by_id($user_id, TRUE))) {
  410. // Check if password correct
  411. $hasher = new PasswordHash(
  412. $this->ci->config->item('phpass_hash_strength', 'tank_auth'),
  413. $this->ci->config->item('phpass_hash_portable', 'tank_auth'));
  414. if ($hasher->CheckPassword($password, $user->password)) { // success
  415. $this->ci->users->delete_user($user_id);
  416. $this->logout();
  417. return TRUE;
  418. } else { // fail
  419. $this->error = array('password' => 'auth_incorrect_password');
  420. }
  421. }
  422. return FALSE;
  423. }
  424. /**
  425. * Get error message.
  426. * Can be invoked after any failed operation such as login or register.
  427. *
  428. * @return string
  429. */
  430. function get_error_message()
  431. {
  432. return $this->error;
  433. }
  434. /**
  435. * Save data for user's autologin
  436. *
  437. * @param int
  438. * @return bool
  439. */
  440. private function create_autologin($user_id)
  441. {
  442. $this->ci->load->helper('cookie');
  443. $key = substr(md5(uniqid(rand().get_cookie($this->ci->config->item('sess_cookie_name')))), 0, 16);
  444. $this->ci->load->model('tank_auth/user_autologin');
  445. $this->ci->user_autologin->purge($user_id);
  446. if ($this->ci->user_autologin->set($user_id, md5($key))) {
  447. set_cookie(array(
  448. 'name' => $this->ci->config->item('autologin_cookie_name', 'tank_auth'),
  449. 'value' => serialize(array('user_id' => $user_id, 'key' => $key)),
  450. 'expire' => $this->ci->config->item('autologin_cookie_life', 'tank_auth'),
  451. ));
  452. return TRUE;
  453. }
  454. return FALSE;
  455. }
  456. /**
  457. * Clear user's autologin data
  458. *
  459. * @return void
  460. */
  461. private function delete_autologin()
  462. {
  463. $this->ci->load->helper('cookie');
  464. if ($cookie = get_cookie($this->ci->config->item('autologin_cookie_name', 'tank_auth'), TRUE)) {
  465. $data = unserialize($cookie);
  466. $this->ci->load->model('tank_auth/user_autologin');
  467. $this->ci->user_autologin->delete($data['user_id'], md5($data['key']));
  468. delete_cookie($this->ci->config->item('autologin_cookie_name', 'tank_auth'));
  469. }
  470. }
  471. /**
  472. * Login user automatically if he/she provides correct autologin verification
  473. *
  474. * @return void
  475. */
  476. private function autologin()
  477. {
  478. if (!$this->is_logged_in() AND !$this->is_logged_in(FALSE)) { // not logged in (as any user)
  479. $this->ci->load->helper('cookie');
  480. if ($cookie = get_cookie($this->ci->config->item('autologin_cookie_name', 'tank_auth'), TRUE)) {
  481. $data = unserialize($cookie);
  482. if (isset($data['key']) AND isset($data['user_id'])) {
  483. $this->ci->load->model('tank_auth/user_autologin');
  484. if (!is_null($user = $this->ci->user_autologin->get($data['user_id'], md5($data['key'])))) {
  485. // Login user
  486. $this->ci->session->set_userdata(array(
  487. 'user_id' => $user->id,
  488. 'username' => $user->username,
  489. 'status' => STATUS_ACTIVATED,
  490. ));
  491. // Renew users cookie to prevent it from expiring
  492. set_cookie(array(
  493. 'name' => $this->ci->config->item('autologin_cookie_name', 'tank_auth'),
  494. 'value' => $cookie,
  495. 'expire' => $this->ci->config->item('autologin_cookie_life', 'tank_auth'),
  496. ));
  497. $this->ci->users->update_login_info(
  498. $user->id,
  499. $this->ci->config->item('login_record_ip', 'tank_auth'),
  500. $this->ci->config->item('login_record_time', 'tank_auth'));
  501. return TRUE;
  502. }
  503. }
  504. }
  505. }
  506. return FALSE;
  507. }
  508. /**
  509. * Check if login attempts exceeded max login attempts (specified in config)
  510. *
  511. * @param string
  512. * @return bool
  513. */
  514. function is_max_login_attempts_exceeded($login)
  515. {
  516. if ($this->ci->config->item('login_count_attempts', 'tank_auth')) {
  517. $this->ci->load->model('tank_auth/login_attempts');
  518. return $this->ci->login_attempts->get_attempts_num($this->ci->input->ip_address(), $login)
  519. >= $this->ci->config->item('login_max_attempts', 'tank_auth');
  520. }
  521. return FALSE;
  522. }
  523. /**
  524. * Increase number of attempts for given IP-address and login
  525. * (if attempts to login is being counted)
  526. *
  527. * @param string
  528. * @return void
  529. */
  530. private function increase_login_attempt($login)
  531. {
  532. if ($this->ci->config->item('login_count_attempts', 'tank_auth')) {
  533. if (!$this->is_max_login_attempts_exceeded($login)) {
  534. $this->ci->load->model('tank_auth/login_attempts');
  535. $this->ci->login_attempts->increase_attempt($this->ci->input->ip_address(), $login);
  536. }
  537. }
  538. }
  539. /**
  540. * Clear all attempt records for given IP-address and login
  541. * (if attempts to login is being counted)
  542. *
  543. * @param string
  544. * @return void
  545. */
  546. private function clear_login_attempts($login)
  547. {
  548. if ($this->ci->config->item('login_count_attempts', 'tank_auth')) {
  549. $this->ci->load->model('tank_auth/login_attempts');
  550. $this->ci->login_attempts->clear_attempts(
  551. $this->ci->input->ip_address(),
  552. $login,
  553. $this->ci->config->item('login_attempt_expire', 'tank_auth'));
  554. }
  555. }
  556. }
  557. /* End of file Tank_auth.php */
  558. /* Location: ./application/libraries/Tank_auth.php */