· 8 years ago · Aug 10, 2018, 08:22 AM
1<?php
2/**
3 * Name: Ion Auth Model
4 * Author: Ben Edmunds
5 * ben.edmunds@gmail.com
6 * @benedmunds
7 *
8 * Added Awesomeness: Phil Sturgeon
9 *
10 * Created: 10.01.2009
11 *
12 * Description: Modified auth system based on redux_auth with extensive customization. This is basically what Redux Auth 2 should be.
13 * Original Author name has been kept but that does not mean that the method has not been modified.
14 *
15 * Requirements: PHP5 or above
16 *
17 * @package CodeIgniter-Ion-Auth
18 * @author Ben Edmunds
19 * @link http://github.com/benedmunds/CodeIgniter-Ion-Auth
20 * @filesource
21 */
22defined('BASEPATH') OR exit('No direct script access allowed');
23
24/**
25 * Class Ion Auth Model
26 * @property Bcrypt $bcrypt The Bcrypt library
27 * @property Ion_auth $ion_auth The Ion_auth library
28 */
29class Ion_auth_model extends CI_Model
30{
31 /**
32 * Holds an array of tables used
33 *
34 * @var array
35 */
36 public $tables = array();
37
38 /**
39 * activation code
40 *
41 * @var string
42 */
43 public $activation_code;
44
45 /**
46 * forgotten password key
47 *
48 * @var string
49 */
50 public $forgotten_password_code;
51
52 /**
53 * new password
54 *
55 * @var string
56 */
57 public $new_password;
58
59 /**
60 * Identity
61 *
62 * @var string
63 */
64 public $identity;
65
66 /**
67 * Where
68 *
69 * @var array
70 */
71 public $_ion_where = array();
72
73 /**
74 * Select
75 *
76 * @var array
77 */
78 public $_ion_select = array();
79
80 /**
81 * Like
82 *
83 * @var array
84 */
85 public $_ion_like = array();
86
87 /**
88 * Limit
89 *
90 * @var string
91 */
92 public $_ion_limit = NULL;
93
94 /**
95 * Offset
96 *
97 * @var string
98 */
99 public $_ion_offset = NULL;
100
101 /**
102 * Order By
103 *
104 * @var string
105 */
106 public $_ion_order_by = NULL;
107
108 /**
109 * Order
110 *
111 * @var string
112 */
113 public $_ion_order = NULL;
114
115 /**
116 * Hooks
117 *
118 * @var object
119 */
120 protected $_ion_hooks;
121
122 /**
123 * Response
124 *
125 * @var string
126 */
127 protected $response = NULL;
128
129 /**
130 * message (uses lang file)
131 *
132 * @var string
133 */
134 protected $messages;
135
136 /**
137 * error message (uses lang file)
138 *
139 * @var string
140 */
141 protected $errors;
142
143 /**
144 * error start delimiter
145 *
146 * @var string
147 */
148 protected $error_start_delimiter;
149
150 /**
151 * error end delimiter
152 *
153 * @var string
154 */
155 protected $error_end_delimiter;
156
157 /**
158 * caching of users and their groups
159 *
160 * @var array
161 */
162 public $_cache_user_in_group = array();
163
164 /**
165 * caching of groups
166 *
167 * @var array
168 */
169 protected $_cache_groups = array();
170
171 /**
172 * Database object
173 *
174 * @var object
175 */
176 protected $db;
177
178 public function __construct()
179 {
180 $this->config->load('ion_auth', TRUE);
181 $this->load->helper('cookie');
182 $this->load->helper('date');
183 $this->lang->load('ion_auth');
184
185 // initialize the database
186 $this->db = $this->load->database($this->config->item('database_group_name', 'ion_auth'), TRUE, TRUE);
187
188 // initialize db tables data
189 $this->tables = $this->config->item('tables', 'ion_auth');
190
191 // initialize data
192 $this->identity_column = $this->config->item('identity', 'ion_auth');
193 $this->store_salt = $this->config->item('store_salt', 'ion_auth');
194 $this->salt_length = $this->config->item('salt_length', 'ion_auth');
195 $this->join = $this->config->item('join', 'ion_auth');
196
197 // initialize hash method options (Bcrypt)
198 $this->hash_method = $this->config->item('hash_method', 'ion_auth');
199 $this->default_rounds = $this->config->item('default_rounds', 'ion_auth');
200 $this->random_rounds = $this->config->item('random_rounds', 'ion_auth');
201 $this->min_rounds = $this->config->item('min_rounds', 'ion_auth');
202 $this->max_rounds = $this->config->item('max_rounds', 'ion_auth');
203
204 // initialize messages and error
205 $this->messages = array();
206 $this->errors = array();
207 $delimiters_source = $this->config->item('delimiters_source', 'ion_auth');
208
209 // load the error delimeters either from the config file or use what's been supplied to form validation
210 if ($delimiters_source === 'form_validation')
211 {
212 // load in delimiters from form_validation
213 // to keep this simple we'll load the value using reflection since these properties are protected
214 $this->load->library('form_validation');
215 $form_validation_class = new ReflectionClass("CI_Form_validation");
216
217 $error_prefix = $form_validation_class->getProperty("_error_prefix");
218 $error_prefix->setAccessible(TRUE);
219 $this->error_start_delimiter = $error_prefix->getValue($this->form_validation);
220 $this->message_start_delimiter = $this->error_start_delimiter;
221
222 $error_suffix = $form_validation_class->getProperty("_error_suffix");
223 $error_suffix->setAccessible(TRUE);
224 $this->error_end_delimiter = $error_suffix->getValue($this->form_validation);
225 $this->message_end_delimiter = $this->error_end_delimiter;
226 }
227 else
228 {
229 // use delimiters from config
230 $this->message_start_delimiter = $this->config->item('message_start_delimiter', 'ion_auth');
231 $this->message_end_delimiter = $this->config->item('message_end_delimiter', 'ion_auth');
232 $this->error_start_delimiter = $this->config->item('error_start_delimiter', 'ion_auth');
233 $this->error_end_delimiter = $this->config->item('error_end_delimiter', 'ion_auth');
234 }
235
236 // initialize our hooks object
237 $this->_ion_hooks = new stdClass;
238
239 // load the bcrypt class if needed
240 if ($this->hash_method == 'bcrypt')
241 {
242 if ($this->random_rounds)
243 {
244 $rand = rand($this->min_rounds,$this->max_rounds);
245 $params = array('rounds' => $rand);
246 }
247 else
248 {
249 $params = array('rounds' => $this->default_rounds);
250 }
251
252 $params['salt_prefix'] = $this->config->item('salt_prefix', 'ion_auth');
253 $this->load->library('bcrypt',$params);
254 }
255
256 $this->trigger_events('model_constructor');
257 }
258
259 /**
260 * Hashes the password to be stored in the database.
261 *
262 * @param string $password
263 * @param bool $salt
264 * @param bool $use_sha1_override
265 *
266 * @return false|string
267 * @author Mathew
268 */
269 public function hash_password($password, $salt = FALSE, $use_sha1_override = FALSE)
270 {
271 if (empty($password))
272 {
273 return FALSE;
274 }
275
276 // bcrypt
277 if ($use_sha1_override === FALSE && $this->hash_method == 'bcrypt')
278 {
279 return $this->bcrypt->hash($password);
280 }
281
282
283 if ($this->store_salt && $salt)
284 {
285 return sha1($password . $salt);
286 }
287 else
288 {
289 $salt = $this->salt();
290 return $salt . substr(sha1($salt . $password), 0, -$this->salt_length);
291 }
292 }
293
294 /**
295 * This function takes a password and validates it
296 * against an entry in the users table.
297 *
298 * @param string|int $id
299 * @param string $password
300 * @param bool $use_sha1_override
301 *
302 * @return bool
303 * @author Mathew
304 */
305 public function hash_password_db($id, $password, $use_sha1_override = FALSE)
306 {
307 if (empty($id) || empty($password))
308 {
309 return FALSE;
310 }
311
312 $this->trigger_events('extra_where');
313
314 $query = $this->db->select('password, salt')
315 ->where('id', $id)
316 ->limit(1)
317 ->order_by('id', 'desc')
318 ->get($this->tables['users']);
319
320 $hash_password_db = $query->row();
321
322 if ($query->num_rows() !== 1)
323 {
324 return FALSE;
325 }
326
327 // bcrypt
328 if ($use_sha1_override === FALSE && $this->hash_method == 'bcrypt')
329 {
330 if ($this->bcrypt->verify($password,$hash_password_db->password))
331 {
332 return TRUE;
333 }
334
335 return FALSE;
336 }
337
338 // sha1
339 if ($this->store_salt)
340 {
341 $db_password = sha1($password . $hash_password_db->salt);
342 }
343 else
344 {
345 $salt = substr($hash_password_db->password, 0, $this->salt_length);
346
347 $db_password = $salt . substr(sha1($salt . $password), 0, -$this->salt_length);
348 }
349
350 if($db_password == $hash_password_db->password)
351 {
352 return TRUE;
353 }
354 else
355 {
356 return FALSE;
357 }
358 }
359
360 /**
361 * Generates a random salt value for forgotten passwords or any other keys. Uses SHA1.
362 *
363 * @param string $password
364 *
365 * @return false|string
366 * @author Mathew
367 */
368 public function hash_code($password)
369 {
370 return $this->hash_password($password, FALSE, TRUE);
371 }
372
373 /**
374 * Generates a random salt value.
375 *
376 * Salt generation code taken from https://github.com/ircmaxell/password_compat/blob/master/lib/password.php
377 *
378 * @return bool|string
379 * @author Anthony Ferrera
380 */
381 public function salt()
382 {
383 $raw_salt_len = 16;
384
385 $buffer = '';
386 $buffer_valid = FALSE;
387
388 if (function_exists('random_bytes'))
389 {
390 $buffer = random_bytes($raw_salt_len);
391 if ($buffer)
392 {
393 $buffer_valid = TRUE;
394 }
395 }
396
397 if (!$buffer_valid && function_exists('mcrypt_create_iv') && !defined('PHALANGER'))
398 {
399 $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM);
400 if ($buffer)
401 {
402 $buffer_valid = TRUE;
403 }
404 }
405
406 if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes'))
407 {
408 $buffer = openssl_random_pseudo_bytes($raw_salt_len);
409 if ($buffer)
410 {
411 $buffer_valid = TRUE;
412 }
413 }
414
415 if (!$buffer_valid && @is_readable('/dev/urandom'))
416 {
417 $f = fopen('/dev/urandom', 'r');
418 $read = strlen($buffer);
419 while ($read < $raw_salt_len)
420 {
421 $buffer .= fread($f, $raw_salt_len - $read);
422 $read = strlen($buffer);
423 }
424 fclose($f);
425 if ($read >= $raw_salt_len)
426 {
427 $buffer_valid = TRUE;
428 }
429 }
430
431 if (!$buffer_valid || strlen($buffer) < $raw_salt_len)
432 {
433 $bl = strlen($buffer);
434 for ($i = 0; $i < $raw_salt_len; $i++)
435 {
436 if ($i < $bl)
437 {
438 $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
439 }
440 else
441 {
442 $buffer .= chr(mt_rand(0, 255));
443 }
444 }
445 }
446
447 $salt = $buffer;
448
449 // encode string with the Base64 variant used by crypt
450 $base64_digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
451 $bcrypt64_digits = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
452 $base64_string = base64_encode($salt);
453 $salt = strtr(rtrim($base64_string, '='), $base64_digits, $bcrypt64_digits);
454
455 $salt = substr($salt, 0, $this->salt_length);
456
457 return $salt;
458 }
459
460 /**
461 * Validates and removes activation code.
462 *
463 * @param int|string $id
464 * @param bool $code
465 *
466 * @return bool
467 * @author Mathew
468 */
469 public function activate($id, $code = FALSE)
470 {
471 $this->trigger_events('pre_activate');
472
473 if ($code !== FALSE)
474 {
475 $query = $this->db->select($this->identity_column)
476 ->where('activation_code', $code)
477 ->where('id', $id)
478 ->limit(1)
479 ->order_by('id', 'desc')
480 ->get($this->tables['users']);
481
482 $query->row();
483
484 if ($query->num_rows() !== 1)
485 {
486 $this->trigger_events(array('post_activate', 'post_activate_unsuccessful'));
487 $this->set_error('activate_unsuccessful');
488 return FALSE;
489 }
490
491 $data = array(
492 'activation_code' => NULL,
493 'active' => 1
494 );
495
496 $this->trigger_events('extra_where');
497 $this->db->update($this->tables['users'], $data, array('id' => $id));
498 }
499 else
500 {
501 $data = array(
502 'activation_code' => NULL,
503 'active' => 1
504 );
505
506 $this->trigger_events('extra_where');
507 $this->db->update($this->tables['users'], $data, array('id' => $id));
508 }
509
510 $return = $this->db->affected_rows() == 1;
511 if ($return)
512 {
513 $this->trigger_events(array('post_activate', 'post_activate_successful'));
514 $this->set_message('activate_successful');
515 }
516 else
517 {
518 $this->trigger_events(array('post_activate', 'post_activate_unsuccessful'));
519 $this->set_error('activate_unsuccessful');
520 }
521
522 return $return;
523 }
524
525
526 /**
527 * Updates a users row with an activation code.
528 *
529 * @param int|string|null $id
530 *
531 * @return bool
532 * @author Mathew
533 */
534 public function deactivate($id = NULL)
535 {
536 $this->trigger_events('deactivate');
537
538 if (!isset($id))
539 {
540 $this->set_error('deactivate_unsuccessful');
541 return FALSE;
542 }
543 else if ($this->ion_auth->logged_in() && $this->user()->row()->id == $id)
544 {
545 $this->set_error('deactivate_current_user_unsuccessful');
546 return FALSE;
547 }
548
549 $activation_code = sha1(md5(microtime()));
550 $this->activation_code = $activation_code;
551
552 $data = array(
553 'activation_code' => $activation_code,
554 'active' => 0
555 );
556
557 $this->trigger_events('extra_where');
558 $this->db->update($this->tables['users'], $data, array('id' => $id));
559
560 $return = $this->db->affected_rows() == 1;
561 if ($return)
562 {
563 $this->set_message('deactivate_successful');
564 }
565 else
566 {
567 $this->set_error('deactivate_unsuccessful');
568 }
569
570 return $return;
571 }
572
573 /**
574 * Finds the user with the given forgotten password code and clears the forgotten password fields
575 *
576 * @param string $code
577 *
578 * @return bool Success
579 */
580 public function clear_forgotten_password_code($code) {
581
582 if (empty($code))
583 {
584 return FALSE;
585 }
586
587 $this->db->where('forgotten_password_code', $code);
588
589 if ($this->db->count_all_results($this->tables['users']) > 0)
590 {
591 $data = array(
592 'forgotten_password_code' => NULL,
593 'forgotten_password_time' => NULL
594 );
595
596 $this->db->update($this->tables['users'], $data, array('forgotten_password_code' => $code));
597
598 return TRUE;
599 }
600
601 return FALSE;
602 }
603
604 /**
605 * Reset password
606 *
607 * @param string $identity
608 * @param string $new
609 *
610 * @return bool
611 * @author Mathew
612 */
613 public function reset_password($identity, $new) {
614 $this->trigger_events('pre_change_password');
615
616 if (!$this->identity_check($identity)) {
617 $this->trigger_events(array('post_change_password', 'post_change_password_unsuccessful'));
618 return FALSE;
619 }
620
621 $this->trigger_events('extra_where');
622
623 $query = $this->db->select('id, password, salt')
624 ->where($this->identity_column, $identity)
625 ->limit(1)
626 ->order_by('id', 'desc')
627 ->get($this->tables['users']);
628
629 if ($query->num_rows() !== 1)
630 {
631 $this->trigger_events(array('post_change_password', 'post_change_password_unsuccessful'));
632 $this->set_error('password_change_unsuccessful');
633 return FALSE;
634 }
635
636 $result = $query->row();
637
638 $new = $this->hash_password($new, $result->salt);
639
640 // store the new password and reset the remember code so all remembered instances have to re-login
641 // also clear the forgotten password code
642 $data = array(
643 'password' => $new,
644 'remember_code' => NULL,
645 'forgotten_password_code' => NULL,
646 'forgotten_password_time' => NULL,
647 );
648
649 $this->trigger_events('extra_where');
650 $this->db->update($this->tables['users'], $data, array($this->identity_column => $identity));
651
652 $return = $this->db->affected_rows() == 1;
653 if ($return)
654 {
655 $this->trigger_events(array('post_change_password', 'post_change_password_successful'));
656 $this->set_message('password_change_successful');
657 }
658 else
659 {
660 $this->trigger_events(array('post_change_password', 'post_change_password_unsuccessful'));
661 $this->set_error('password_change_unsuccessful');
662 }
663
664 return $return;
665 }
666
667 /**
668 * Change password
669 *
670 * @param string $identity
671 * @param string $old
672 * @param string $new
673 *
674 * @return bool
675 * @author Mathew
676 */
677 public function change_password($identity, $old, $new)
678 {
679 $this->trigger_events('pre_change_password');
680
681 $this->trigger_events('extra_where');
682
683 $query = $this->db->select('id, password, salt')
684 ->where($this->identity_column, $identity)
685 ->limit(1)
686 ->order_by('id', 'desc')
687 ->get($this->tables['users']);
688
689 if ($query->num_rows() !== 1)
690 {
691 $this->trigger_events(array('post_change_password', 'post_change_password_unsuccessful'));
692 $this->set_error('password_change_unsuccessful');
693 return FALSE;
694 }
695
696 $user = $query->row();
697
698 $old_password_matches = $this->hash_password_db($user->id, $old);
699
700 if ($old_password_matches === TRUE)
701 {
702 // store the new password and reset the remember code so all remembered instances have to re-login
703 $hashed_new_password = $this->hash_password($new, $user->salt);
704 $data = array(
705 'password' => $hashed_new_password,
706 'remember_code' => NULL,
707 );
708
709 $this->trigger_events('extra_where');
710
711 $successfully_changed_password_in_db = $this->db->update($this->tables['users'], $data, array($this->identity_column => $identity));
712 if ($successfully_changed_password_in_db)
713 {
714 $this->trigger_events(array('post_change_password', 'post_change_password_successful'));
715 $this->set_message('password_change_successful');
716 }
717 else
718 {
719 $this->trigger_events(array('post_change_password', 'post_change_password_unsuccessful'));
720 $this->set_error('password_change_unsuccessful');
721 }
722
723 return $successfully_changed_password_in_db;
724 }
725
726 $this->set_error('password_change_unsuccessful');
727 return FALSE;
728 }
729
730 /**
731 * Checks username
732 *
733 * @param string $username
734 *
735 * @return bool
736 * @author Mathew
737 */
738 public function username_check($username = '')
739 {
740 $this->trigger_events('username_check');
741
742 if (empty($username))
743 {
744 return FALSE;
745 }
746
747 $this->trigger_events('extra_where');
748
749 return $this->db->where('username', $username)
750 ->limit(1)
751 ->count_all_results($this->tables['users']) > 0;
752 }
753
754 /**
755 * Checks email
756 *
757 * @param string $email
758 *
759 * @return bool
760 * @author Mathew
761 */
762 public function email_check($email = '')
763 {
764 $this->trigger_events('email_check');
765
766 if (empty($email))
767 {
768 return FALSE;
769 }
770
771 $this->trigger_events('extra_where');
772
773 return $this->db->where('email', $email)
774 ->limit(1)
775 ->count_all_results($this->tables['users']) > 0;
776 }
777
778 /**
779 * Identity check
780 *
781 * @return bool
782 * @author Mathew
783 */
784 public function identity_check($identity = '')
785 {
786 $this->trigger_events('identity_check');
787
788 if (empty($identity))
789 {
790 return FALSE;
791 }
792
793 return $this->db->where($this->identity_column, $identity)
794 ->limit(1)
795 ->count_all_results($this->tables['users']) > 0;
796 }
797
798 /**
799 * Insert a forgotten password key.
800 *
801 * @param string $identity
802 *
803 * @return bool
804 * @author Mathew
805 * @updated Ryan
806 */
807 public function forgotten_password($identity)
808 {
809 if (empty($identity))
810 {
811 $this->trigger_events(array('post_forgotten_password', 'post_forgotten_password_unsuccessful'));
812 return FALSE;
813 }
814
815 // All some more randomness
816 $activation_code_part = "";
817 if (function_exists("openssl_random_pseudo_bytes"))
818 {
819 $activation_code_part = openssl_random_pseudo_bytes(128);
820 }
821
822 for ($i = 0; $i < 1024; $i++)
823 {
824 $activation_code_part = sha1($activation_code_part . mt_rand() . microtime());
825 }
826
827 $key = $this->hash_code($activation_code_part . $identity);
828
829 // If enable query strings is set, then we need to replace any unsafe characters so that the code can still work
830 if ($key != '' && $this->config->item('permitted_uri_chars') != '' && $this->config->item('enable_query_strings') == FALSE)
831 {
832 // preg_quote() in PHP 5.3 escapes -, so the str_replace() and addition of - to preg_quote() is to maintain backwards
833 // compatibility as many are unaware of how characters in the permitted_uri_chars will be parsed as a regex pattern
834 if (!preg_match("|^[" . str_replace(array('\\-', '\-'), '-', preg_quote($this->config->item('permitted_uri_chars'), '-')) . "]+$|i", $key))
835 {
836 $key = preg_replace("/[^" . $this->config->item('permitted_uri_chars') . "]+/i", "-", $key);
837 }
838 }
839
840 // Limit to 40 characters since that's how our DB field is setup
841 $this->forgotten_password_code = substr($key, 0, 40);
842
843 $this->trigger_events('extra_where');
844
845 $update = array(
846 'forgotten_password_code' => $key,
847 'forgotten_password_time' => time()
848 );
849
850 $this->db->update($this->tables['users'], $update, array($this->identity_column => $identity));
851
852 $return = $this->db->affected_rows() == 1;
853
854 if ($return)
855 {
856 $this->trigger_events(array('post_forgotten_password', 'post_forgotten_password_successful'));
857 }
858 else
859 {
860 $this->trigger_events(array('post_forgotten_password', 'post_forgotten_password_unsuccessful'));
861 }
862
863 return $return;
864 }
865
866 /**
867 * Forgotten Password Complete
868 *
869 * @param string $code
870 * @param bool $salt
871 *
872 * @return string
873 * @author Mathew
874 */
875 public function forgotten_password_complete($code, $salt = FALSE)
876 {
877 $this->trigger_events('pre_forgotten_password_complete');
878
879 if (empty($code))
880 {
881 $this->trigger_events(array('post_forgotten_password_complete', 'post_forgotten_password_complete_unsuccessful'));
882 return FALSE;
883 }
884
885 $profile = $this->where('forgotten_password_code', $code)->users()->row(); //pass the code to profile
886
887 if ($profile)
888 {
889
890 if ($this->config->item('forgot_password_expiration', 'ion_auth') > 0)
891 {
892 //Make sure it isn't expired
893 $expiration = $this->config->item('forgot_password_expiration', 'ion_auth');
894 if (time() - $profile->forgotten_password_time > $expiration)
895 {
896 //it has expired
897 $this->set_error('forgot_password_expired');
898 $this->trigger_events(array('post_forgotten_password_complete', 'post_forgotten_password_complete_unsuccessful'));
899 return FALSE;
900 }
901 }
902
903 $password = $this->salt();
904
905 $data = array(
906 'password' => $this->hash_password($password, $salt),
907 'forgotten_password_code' => NULL,
908 'active' => 1,
909 );
910
911 $this->db->update($this->tables['users'], $data, array('forgotten_password_code' => $code));
912
913 $this->trigger_events(array('post_forgotten_password_complete', 'post_forgotten_password_complete_successful'));
914 return $password;
915 }
916
917 $this->trigger_events(array('post_forgotten_password_complete', 'post_forgotten_password_complete_unsuccessful'));
918 return FALSE;
919 }
920
921 /**
922 * Register
923 *
924 * @param string $identity
925 * @param string $password
926 * @param string $email
927 * @param array $additional_data
928 * @param array $groups
929 *
930 * @return bool
931 * @author Mathew
932 */
933 public function register($identity, $password, $email, $additional_data = array(), $groups = array())
934 {
935 $this->trigger_events('pre_register');
936
937 $manual_activation = $this->config->item('manual_activation', 'ion_auth');
938
939 if ($this->identity_check($identity))
940 {
941 $this->set_error('account_creation_duplicate_identity');
942 return FALSE;
943 }
944 else if (!$this->config->item('default_group', 'ion_auth') && empty($groups))
945 {
946 $this->set_error('account_creation_missing_default_group');
947 return FALSE;
948 }
949
950 // check if the default set in config exists in database
951 $query = $this->db->get_where($this->tables['groups'], array('name' => $this->config->item('default_group', 'ion_auth')), 1)->row();
952 if (!isset($query->id) && empty($groups))
953 {
954 $this->set_error('account_creation_invalid_default_group');
955 return FALSE;
956 }
957
958 // capture default group details
959 $default_group = $query;
960
961 // IP Address
962 $ip_address = $this->_prepare_ip($this->input->ip_address());
963 $salt = $this->store_salt ? $this->salt() : FALSE;
964 $password = $this->hash_password($password, $salt);
965
966 // Users table.
967 $data = array(
968 $this->identity_column => $identity,
969 'username' => $identity,
970 'password' => $password,
971 'email' => $email,
972 'ip_address' => $ip_address,
973 'created_on' => time(),
974 'active' => ($manual_activation === FALSE ? 1 : 0)
975 );
976
977 if ($this->store_salt)
978 {
979 $data['salt'] = $salt;
980 }
981
982 // filter out any data passed that doesnt have a matching column in the users table
983 // and merge the set user data and the additional data
984 $user_data = array_merge($this->_filter_data($this->tables['users'], $additional_data), $data);
985
986 $this->trigger_events('extra_set');
987
988 $this->db->insert($this->tables['users'], $user_data);
989
990 $id = $this->db->insert_id($this->tables['users'] . '_id_seq');
991
992 // add in groups array if it doesn't exists and stop adding into default group if default group ids are set
993 if (isset($default_group->id) && empty($groups))
994 {
995 $groups[] = $default_group->id;
996 }
997
998 if (!empty($groups))
999 {
1000 // add to groups
1001 foreach ($groups as $group)
1002 {
1003 $this->add_to_group($group, $id);
1004 }
1005 }
1006
1007 $this->trigger_events('post_register');
1008
1009 return (isset($id)) ? $id : FALSE;
1010 }
1011
1012 /**
1013 * login
1014 *
1015 * @param string $identity
1016 * @param string $password
1017 * @param bool $remember
1018 *
1019 * @return bool
1020 * @author Mathew
1021 */
1022 public function login($identity, $password, $remember=FALSE)
1023 {
1024 $this->trigger_events('pre_login');
1025
1026 if (empty($identity) || empty($password))
1027 {
1028 $this->set_error('login_unsuccessful');
1029 return FALSE;
1030 }
1031
1032 $this->trigger_events('extra_where');
1033
1034 $query = $this->db->select($this->identity_column . ', email, id, password, active, last_login')
1035 ->where($this->identity_column, $identity)
1036 ->limit(1)
1037 ->order_by('id', 'desc')
1038 ->get($this->tables['users']);
1039
1040 if ($this->is_max_login_attempts_exceeded($identity))
1041 {
1042 // Hash something anyway, just to take up time
1043 $this->hash_password($password);
1044
1045 $this->trigger_events('post_login_unsuccessful');
1046 $this->set_error('login_timeout');
1047
1048 return FALSE;
1049 }
1050
1051 if ($query->num_rows() === 1)
1052 {
1053 $user = $query->row();
1054
1055 $password = $this->hash_password_db($user->id, $password);
1056
1057 if ($password === TRUE)
1058 {
1059 if ($user->active == 0)
1060 {
1061 $this->trigger_events('post_login_unsuccessful');
1062 $this->set_error('login_unsuccessful_not_active');
1063
1064 return FALSE;
1065 }
1066
1067 $this->set_session($user);
1068
1069 $this->update_last_login($user->id);
1070
1071 $this->clear_login_attempts($identity);
1072
1073 if ($remember && $this->config->item('remember_users', 'ion_auth'))
1074 {
1075 $this->remember_user($user->id);
1076 }
1077
1078 // Regenerate the session (for security purpose: to avoid session fixation)
1079 $this->_regenerate_session();
1080
1081 $this->trigger_events(array('post_login', 'post_login_successful'));
1082 $this->set_message('login_successful');
1083
1084 return TRUE;
1085 }
1086 }
1087
1088 // Hash something anyway, just to take up time
1089 $this->hash_password($password);
1090
1091 $this->increase_login_attempts($identity);
1092
1093 $this->trigger_events('post_login_unsuccessful');
1094 $this->set_error('login_unsuccessful');
1095
1096 return FALSE;
1097 }
1098
1099 /**
1100 * Verifies if the session should be rechecked according to the configuration item recheck_timer. If it does, then
1101 * it will check if the user is still active
1102 * @return bool
1103 */
1104 public function recheck_session()
1105 {
1106 $recheck = (NULL !== $this->config->item('recheck_timer', 'ion_auth')) ? $this->config->item('recheck_timer', 'ion_auth') : 0;
1107
1108 if ($recheck !== 0)
1109 {
1110 $last_login = $this->session->userdata('last_check');
1111 if ($last_login + $recheck < time())
1112 {
1113 $query = $this->db->select('id')
1114 ->where(array($this->identity_column => $this->session->userdata('identity'), 'active' => '1'))
1115 ->limit(1)
1116 ->order_by('id', 'desc')
1117 ->get($this->tables['users']);
1118 if ($query->num_rows() === 1)
1119 {
1120 $this->session->set_userdata('last_check', time());
1121 }
1122 else
1123 {
1124 $this->trigger_events('logout');
1125
1126 $identity = $this->config->item('identity', 'ion_auth');
1127
1128 if (substr(CI_VERSION, 0, 1) == '2')
1129 {
1130 $this->session->unset_userdata(array($identity => '', 'id' => '', 'user_id' => ''));
1131 }
1132 else
1133 {
1134 $this->session->unset_userdata(array($identity, 'id', 'user_id'));
1135 }
1136 return FALSE;
1137 }
1138 }
1139 }
1140
1141 return (bool)$this->session->userdata('identity');
1142 }
1143
1144 /**
1145 * is_max_login_attempts_exceeded
1146 * Based on code from Tank Auth, by Ilya Konyukhov (https://github.com/ilkon/Tank-Auth)
1147 *
1148 * @param string $identity user's identity
1149 * @param string|null $ip_address IP address
1150 * Only used if track_login_ip_address is set to TRUE.
1151 * If NULL (default value), the current IP address is used.
1152 * Use get_last_attempt_ip($identity) to retrieve a user's last IP
1153 *
1154 * @return boolean
1155 */
1156 public function is_max_login_attempts_exceeded($identity, $ip_address = NULL)
1157 {
1158 if ($this->config->item('track_login_attempts', 'ion_auth'))
1159 {
1160 $max_attempts = $this->config->item('maximum_login_attempts', 'ion_auth');
1161 if ($max_attempts > 0)
1162 {
1163 $attempts = $this->get_attempts_num($identity, $ip_address);
1164 return $attempts >= $max_attempts;
1165 }
1166 }
1167 return FALSE;
1168 }
1169
1170 /**
1171 * Get number of login attempts for the given IP-address or identity
1172 * Based on code from Tank Auth, by Ilya Konyukhov (https://github.com/ilkon/Tank-Auth)
1173 *
1174 * @param string $identity User's identity
1175 * @param string|null $ip_address IP address
1176 * Only used if track_login_ip_address is set to TRUE.
1177 * If NULL (default value), the current IP address is used.
1178 * Use get_last_attempt_ip($identity) to retrieve a user's last IP
1179 *
1180 * @return int
1181 */
1182 public function get_attempts_num($identity, $ip_address = NULL)
1183 {
1184 if ($this->config->item('track_login_attempts', 'ion_auth'))
1185 {
1186 $this->db->select('1', FALSE);
1187 $this->db->where('login', $identity);
1188 if ($this->config->item('track_login_ip_address', 'ion_auth'))
1189 {
1190 if (!isset($ip_address))
1191 {
1192 $ip_address = $this->_prepare_ip($this->input->ip_address());
1193 }
1194 $this->db->where('ip_address', $ip_address);
1195 }
1196 $this->db->where('time >', time() - $this->config->item('lockout_time', 'ion_auth'), FALSE);
1197 $qres = $this->db->get($this->tables['login_attempts']);
1198 return $qres->num_rows();
1199 }
1200 return 0;
1201 }
1202
1203 /**
1204 * @deprecated This function is now only a wrapper for is_max_login_attempts_exceeded() since it only retrieve
1205 * attempts within the given period.
1206 *
1207 * @param string $identity User's identity
1208 * @param string|null $ip_address IP address
1209 * Only used if track_login_ip_address is set to TRUE.
1210 * If NULL (default value), the current IP address is used.
1211 * Use get_last_attempt_ip($identity) to retrieve a user's last IP
1212 *
1213 * @return boolean Whether an account is locked due to excessive login attempts within a given period
1214 */
1215 public function is_time_locked_out($identity, $ip_address = NULL)
1216 {
1217 return $this->is_max_login_attempts_exceeded($identity, $ip_address);
1218 }
1219
1220 /**
1221 * @deprecated This function is now only a wrapper for is_max_login_attempts_exceeded() since it only retrieve
1222 * attempts within the given period.
1223 *
1224 * @param string $identity User's identity
1225 * @param string|null $ip_address IP address
1226 * Only used if track_login_ip_address is set to TRUE.
1227 * If NULL (default value), the current IP address is used.
1228 * Use get_last_attempt_ip($identity) to retrieve a user's last IP
1229 *
1230 * @return int The time of the last login attempt for a given IP-address or identity
1231 */
1232 public function get_last_attempt_time($identity, $ip_address = NULL)
1233 {
1234 if ($this->config->item('track_login_attempts', 'ion_auth'))
1235 {
1236 $this->db->select('time');
1237 $this->db->where('login', $identity);
1238 if ($this->config->item('track_login_ip_address', 'ion_auth'))
1239 {
1240 if (!isset($ip_address))
1241 {
1242 $ip_address = $this->_prepare_ip($this->input->ip_address());
1243 }
1244 $this->db->where('ip_address', $ip_address);
1245 }
1246 $this->db->order_by('id', 'desc');
1247 $qres = $this->db->get($this->tables['login_attempts'], 1);
1248
1249 if ($qres->num_rows() > 0)
1250 {
1251 return $qres->row()->time;
1252 }
1253 }
1254
1255 return 0;
1256 }
1257
1258 /**
1259 * Get the IP address of the last time a login attempt occured from given identity
1260 *
1261 * @param string $identity User's identity
1262 *
1263 * @return string
1264 */
1265 public function get_last_attempt_ip($identity)
1266 {
1267 if ($this->config->item('track_login_attempts', 'ion_auth') && $this->config->item('track_login_ip_address', 'ion_auth'))
1268 {
1269 $this->db->select('ip_address');
1270 $this->db->where('login', $identity);
1271 $this->db->order_by('id', 'desc');
1272 $qres = $this->db->get($this->tables['login_attempts'], 1);
1273
1274 if ($qres->num_rows() > 0)
1275 {
1276 return $qres->row()->ip_address;
1277 }
1278 }
1279
1280 return '';
1281 }
1282
1283 /**
1284 * Based on code from Tank Auth, by Ilya Konyukhov (https://github.com/ilkon/Tank-Auth)
1285 *
1286 * Note: the current IP address will be used if track_login_ip_address config value is TRUE
1287 *
1288 * @param string $identity User's identity
1289 *
1290 * @return bool
1291 */
1292 public function increase_login_attempts($identity)
1293 {
1294 if ($this->config->item('track_login_attempts', 'ion_auth'))
1295 {
1296 $data = array('ip_address' => '', 'login' => $identity, 'time' => time());
1297 if ($this->config->item('track_login_ip_address', 'ion_auth'))
1298 {
1299 $data['ip_address'] = $this->_prepare_ip($this->input->ip_address());
1300 }
1301 return $this->db->insert($this->tables['login_attempts'], $data);
1302 }
1303 return FALSE;
1304 }
1305
1306 /**
1307 * clear_login_attempts
1308 * Based on code from Tank Auth, by Ilya Konyukhov (https://github.com/ilkon/Tank-Auth)
1309 *
1310 * @param string $identity User's identity
1311 * @param int $old_attempts_expire_period In seconds, any attempts older than this value will be removed.
1312 * It is used for regularly purging the attempts table.
1313 * (for security reason, minimum value is lockout_time config value)
1314 * @param string|null $ip_address IP address
1315 * Only used if track_login_ip_address is set to TRUE.
1316 * If NULL (default value), the current IP address is used.
1317 * Use get_last_attempt_ip($identity) to retrieve a user's last IP
1318 *
1319 * @return bool
1320 */
1321 public function clear_login_attempts($identity, $old_attempts_expire_period = 86400, $ip_address = NULL)
1322 {
1323 if ($this->config->item('track_login_attempts', 'ion_auth'))
1324 {
1325 // Make sure $old_attempts_expire_period is at least equals to lockout_time
1326 $old_attempts_expire_period = max($old_attempts_expire_period, $this->config->item('lockout_time', 'ion_auth'));
1327
1328 $this->db->where('login', $identity);
1329 if ($this->config->item('track_login_ip_address', 'ion_auth'))
1330 {
1331 if (!isset($ip_address))
1332 {
1333 $ip_address = $this->_prepare_ip($this->input->ip_address());
1334 }
1335 $this->db->where('ip_address', $ip_address);
1336 }
1337 // Purge obsolete login attempts
1338 $this->db->or_where('time <', time() - $old_attempts_expire_period, FALSE);
1339
1340 return $this->db->delete($this->tables['login_attempts']);
1341 }
1342 return FALSE;
1343 }
1344
1345 /**
1346 * @param int $limit
1347 *
1348 * @return static
1349 */
1350 public function limit($limit)
1351 {
1352 $this->trigger_events('limit');
1353 $this->_ion_limit = $limit;
1354
1355 return $this;
1356 }
1357
1358 /**
1359 * @param int $offset
1360 *
1361 * @return static
1362 */
1363 public function offset($offset)
1364 {
1365 $this->trigger_events('offset');
1366 $this->_ion_offset = $offset;
1367
1368 return $this;
1369 }
1370
1371 /**
1372 * @param array|string $where
1373 * @param null|string $value
1374 *
1375 * @return static
1376 */
1377 public function where($where, $value = NULL)
1378 {
1379 $this->trigger_events('where');
1380
1381 if (!is_array($where))
1382 {
1383 $where = array($where => $value);
1384 }
1385
1386 array_push($this->_ion_where, $where);
1387
1388 return $this;
1389 }
1390
1391 /**
1392 * @param string $like
1393 * @param string|null $value
1394 * @param string $position
1395 *
1396 * @return static
1397 */
1398 public function like($like, $value = NULL, $position = 'both')
1399 {
1400 $this->trigger_events('like');
1401
1402 array_push($this->_ion_like, array(
1403 'like' => $like,
1404 'value' => $value,
1405 'position' => $position
1406 ));
1407
1408 return $this;
1409 }
1410
1411 /**
1412 * @param array|string $select
1413 *
1414 * @return static
1415 */
1416 public function select($select)
1417 {
1418 $this->trigger_events('select');
1419
1420 $this->_ion_select[] = $select;
1421
1422 return $this;
1423 }
1424
1425 /**
1426 * @param string $by
1427 * @param string $order
1428 *
1429 * @return static
1430 */
1431 public function order_by($by, $order='desc')
1432 {
1433 $this->trigger_events('order_by');
1434
1435 $this->_ion_order_by = $by;
1436 $this->_ion_order = $order;
1437
1438 return $this;
1439 }
1440
1441 /**
1442 * @return object|mixed
1443 */
1444 public function row()
1445 {
1446 $this->trigger_events('row');
1447
1448 $row = $this->response->row();
1449
1450 return $row;
1451 }
1452
1453 /**
1454 * @return array|mixed
1455 */
1456 public function row_array()
1457 {
1458 $this->trigger_events(array('row', 'row_array'));
1459
1460 $row = $this->response->row_array();
1461
1462 return $row;
1463 }
1464
1465 /**
1466 * @return mixed
1467 */
1468 public function result()
1469 {
1470 $this->trigger_events('result');
1471
1472 $result = $this->response->result();
1473
1474 return $result;
1475 }
1476
1477 /**
1478 * @return array|mixed
1479 */
1480 public function result_array()
1481 {
1482 $this->trigger_events(array('result', 'result_array'));
1483
1484 $result = $this->response->result_array();
1485
1486 return $result;
1487 }
1488
1489 /**
1490 * @return int
1491 */
1492 public function num_rows()
1493 {
1494 $this->trigger_events(array('num_rows'));
1495
1496 $result = $this->response->num_rows();
1497
1498 return $result;
1499 }
1500
1501 /**
1502 * users
1503 *
1504 * @param array|null $groups
1505 *
1506 * @return static
1507 * @author Ben Edmunds
1508 */
1509 public function users($groups = NULL)
1510 {
1511 $this->trigger_events('users');
1512
1513 if (isset($this->_ion_select) && !empty($this->_ion_select))
1514 {
1515 foreach ($this->_ion_select as $select)
1516 {
1517 $this->db->select($select);
1518 }
1519
1520 $this->_ion_select = array();
1521 }
1522 else
1523 {
1524 // default selects
1525 $this->db->select(array(
1526 $this->tables['users'].'.*',
1527 $this->tables['users'].'.id as id',
1528 $this->tables['users'].'.id as user_id'
1529 ));
1530 }
1531
1532 // filter by group id(s) if passed
1533 if (isset($groups))
1534 {
1535 // build an array if only one group was passed
1536 if (!is_array($groups))
1537 {
1538 $groups = Array($groups);
1539 }
1540
1541 // join and then run a where_in against the group ids
1542 if (isset($groups) && !empty($groups))
1543 {
1544 $this->db->distinct();
1545 $this->db->join(
1546 $this->tables['users_groups'],
1547 $this->tables['users_groups'].'.'.$this->join['users'].'='.$this->tables['users'].'.id',
1548 'inner'
1549 );
1550 }
1551
1552 // verify if group name or group id was used and create and put elements in different arrays
1553 $group_ids = array();
1554 $group_names = array();
1555 foreach($groups as $group)
1556 {
1557 if(is_numeric($group)) $group_ids[] = $group;
1558 else $group_names[] = $group;
1559 }
1560 $or_where_in = (!empty($group_ids) && !empty($group_names)) ? 'or_where_in' : 'where_in';
1561 // if group name was used we do one more join with groups
1562 if(!empty($group_names))
1563 {
1564 $this->db->join($this->tables['groups'], $this->tables['users_groups'] . '.' . $this->join['groups'] . ' = ' . $this->tables['groups'] . '.id', 'inner');
1565 $this->db->where_in($this->tables['groups'] . '.name', $group_names);
1566 }
1567 if(!empty($group_ids))
1568 {
1569 $this->db->{$or_where_in}($this->tables['users_groups'].'.'.$this->join['groups'], $group_ids);
1570 }
1571 }
1572
1573 $this->trigger_events('extra_where');
1574
1575 // run each where that was passed
1576 if (isset($this->_ion_where) && !empty($this->_ion_where))
1577 {
1578 foreach ($this->_ion_where as $where)
1579 {
1580 $this->db->where($where);
1581 }
1582
1583 $this->_ion_where = array();
1584 }
1585
1586 if (isset($this->_ion_like) && !empty($this->_ion_like))
1587 {
1588 foreach ($this->_ion_like as $like)
1589 {
1590 $this->db->or_like($like['like'], $like['value'], $like['position']);
1591 }
1592
1593 $this->_ion_like = array();
1594 }
1595
1596 if (isset($this->_ion_limit) && isset($this->_ion_offset))
1597 {
1598 $this->db->limit($this->_ion_limit, $this->_ion_offset);
1599
1600 $this->_ion_limit = NULL;
1601 $this->_ion_offset = NULL;
1602 }
1603 else if (isset($this->_ion_limit))
1604 {
1605 $this->db->limit($this->_ion_limit);
1606
1607 $this->_ion_limit = NULL;
1608 }
1609
1610 // set the order
1611 if (isset($this->_ion_order_by) && isset($this->_ion_order))
1612 {
1613 $this->db->order_by($this->_ion_order_by, $this->_ion_order);
1614
1615 $this->_ion_order = NULL;
1616 $this->_ion_order_by = NULL;
1617 }
1618
1619 $this->response = $this->db->get($this->tables['users']);
1620
1621 return $this;
1622 }
1623
1624 /**
1625 * user
1626 *
1627 * @param int|string|null $id
1628 *
1629 * @return static
1630 * @author Ben Edmunds
1631 */
1632 public function user($id = NULL)
1633 {
1634 $this->trigger_events('user');
1635
1636 // if no id was passed use the current users id
1637 $id = isset($id) ? $id : $this->session->userdata('user_id');
1638
1639 $this->limit(1);
1640 $this->order_by($this->tables['users'].'.id', 'desc');
1641 $this->where($this->tables['users'].'.id', $id);
1642
1643 $this->users();
1644
1645 return $this;
1646 }
1647
1648 /**
1649 * get_users_groups
1650 *
1651 * @param int|string|bool $id
1652 *
1653 * @return CI_DB_result
1654 * @author Ben Edmunds
1655 */
1656 public function get_users_groups($id = FALSE)
1657 {
1658 $this->trigger_events('get_users_group');
1659
1660 // if no id was passed use the current users id
1661 $id || $id = $this->session->userdata('user_id');
1662
1663 return $this->db->select($this->tables['users_groups'].'.'.$this->join['groups'].' as id, '.$this->tables['groups'].'.name, '.$this->tables['groups'].'.description')
1664 ->where($this->tables['users_groups'].'.'.$this->join['users'], $id)
1665 ->join($this->tables['groups'], $this->tables['users_groups'].'.'.$this->join['groups'].'='.$this->tables['groups'].'.id')
1666 ->get($this->tables['users_groups']);
1667 }
1668
1669 /**
1670 * add_to_group
1671 *
1672 * @param array|int|float|string $group_ids
1673 * @param bool|int|float|string $user_id
1674 *
1675 * @return int
1676 * @author Ben Edmunds
1677 */
1678 public function add_to_group($group_ids, $user_id = FALSE)
1679 {
1680 $this->trigger_events('add_to_group');
1681
1682 // if no id was passed use the current users id
1683 $user_id || $user_id = $this->session->userdata('user_id');
1684
1685 if(!is_array($group_ids))
1686 {
1687 $group_ids = array($group_ids);
1688 }
1689
1690 $return = 0;
1691
1692 // Then insert each into the database
1693 foreach ($group_ids as $group_id)
1694 {
1695 // Cast to float to support bigint data type
1696 if ($this->db->insert(
1697 $this->tables['users_groups'],
1698 array(
1699 $this->join['groups'] => (float)$group_id,
1700 $this->join['users'] => (float)$user_id
1701 )
1702 )
1703 )
1704 {
1705 if (isset($this->_cache_groups[$group_id]))
1706 {
1707 $group_name = $this->_cache_groups[$group_id];
1708 }
1709 else
1710 {
1711 $group = $this->group($group_id)->result();
1712 $group_name = $group[0]->name;
1713 $this->_cache_groups[$group_id] = $group_name;
1714 }
1715 $this->_cache_user_in_group[$user_id][$group_id] = $group_name;
1716
1717 // Return the number of groups added
1718 $return++;
1719 }
1720 }
1721
1722 return $return;
1723 }
1724
1725 /**
1726 * remove_from_group
1727 *
1728 * @param array|int|float|string|bool $group_ids
1729 * @param int|float|string|bool $user_id
1730 *
1731 * @return bool
1732 * @author Ben Edmunds
1733 */
1734 public function remove_from_group($group_ids = FALSE, $user_id = FALSE)
1735 {
1736 $this->trigger_events('remove_from_group');
1737
1738 // user id is required
1739 if (empty($user_id))
1740 {
1741 return FALSE;
1742 }
1743
1744 // if group id(s) are passed remove user from the group(s)
1745 if (!empty($group_ids))
1746 {
1747 if (!is_array($group_ids))
1748 {
1749 $group_ids = array($group_ids);
1750 }
1751
1752 foreach ($group_ids as $group_id)
1753 {
1754 // Cast to float to support bigint data type
1755 $this->db->delete(
1756 $this->tables['users_groups'],
1757 array($this->join['groups'] => (float)$group_id, $this->join['users'] => (float)$user_id)
1758 );
1759 if (isset($this->_cache_user_in_group[$user_id]) && isset($this->_cache_user_in_group[$user_id][$group_id]))
1760 {
1761 unset($this->_cache_user_in_group[$user_id][$group_id]);
1762 }
1763 }
1764
1765 $return = TRUE;
1766 }
1767 // otherwise remove user from all groups
1768 else
1769 {
1770 // Cast to float to support bigint data type
1771 if ($return = $this->db->delete($this->tables['users_groups'], array($this->join['users'] => (float)$user_id)))
1772 {
1773 $this->_cache_user_in_group[$user_id] = array();
1774 }
1775 }
1776 return $return;
1777 }
1778
1779 /**
1780 * groups
1781 *
1782 * @return static
1783 * @author Ben Edmunds
1784 */
1785 public function groups()
1786 {
1787 $this->trigger_events('groups');
1788
1789 // run each where that was passed
1790 if (isset($this->_ion_where) && !empty($this->_ion_where))
1791 {
1792 foreach ($this->_ion_where as $where)
1793 {
1794 $this->db->where($where);
1795 }
1796 $this->_ion_where = array();
1797 }
1798
1799 if (isset($this->_ion_limit) && isset($this->_ion_offset))
1800 {
1801 $this->db->limit($this->_ion_limit, $this->_ion_offset);
1802
1803 $this->_ion_limit = NULL;
1804 $this->_ion_offset = NULL;
1805 }
1806 else if (isset($this->_ion_limit))
1807 {
1808 $this->db->limit($this->_ion_limit);
1809
1810 $this->_ion_limit = NULL;
1811 }
1812
1813 // set the order
1814 if (isset($this->_ion_order_by) && isset($this->_ion_order))
1815 {
1816 $this->db->order_by($this->_ion_order_by, $this->_ion_order);
1817 }
1818
1819 $this->response = $this->db->get($this->tables['groups']);
1820
1821 return $this;
1822 }
1823
1824 /**
1825 * group
1826 *
1827 * @param int|string|null $id
1828 *
1829 * @return static
1830 * @author Ben Edmunds
1831 */
1832 public function group($id = NULL)
1833 {
1834 $this->trigger_events('group');
1835
1836 if (isset($id))
1837 {
1838 $this->where($this->tables['groups'].'.id', $id);
1839 }
1840
1841 $this->limit(1);
1842 $this->order_by('id', 'desc');
1843
1844 return $this->groups();
1845 }
1846
1847 /**
1848 * update
1849 *
1850 * @param int|string $id
1851 * @param array $data
1852 *
1853 * @return bool
1854 * @author Phil Sturgeon
1855 */
1856 public function update($id, array $data)
1857 {
1858 $this->trigger_events('pre_update_user');
1859
1860 $user = $this->user($id)->row();
1861
1862 $this->db->trans_begin();
1863
1864 if (array_key_exists($this->identity_column, $data) && $this->identity_check($data[$this->identity_column]) && $user->{$this->identity_column} !== $data[$this->identity_column])
1865 {
1866 $this->db->trans_rollback();
1867 $this->set_error('account_creation_duplicate_identity');
1868
1869 $this->trigger_events(array('post_update_user', 'post_update_user_unsuccessful'));
1870 $this->set_error('update_unsuccessful');
1871
1872 return FALSE;
1873 }
1874
1875 // Filter the data passed
1876 $data = $this->_filter_data($this->tables['users'], $data);
1877
1878 if (array_key_exists($this->identity_column, $data) || array_key_exists('password', $data) || array_key_exists('email', $data))
1879 {
1880 if (array_key_exists('password', $data))
1881 {
1882 if( ! empty($data['password']))
1883 {
1884 $data['password'] = $this->hash_password($data['password'], $user->salt);
1885 }
1886 else
1887 {
1888 // unset password so it doesn't effect database entry if no password passed
1889 unset($data['password']);
1890 }
1891 }
1892 }
1893
1894 $this->trigger_events('extra_where');
1895 $this->db->update($this->tables['users'], $data, array('id' => $user->id));
1896
1897 if ($this->db->trans_status() === FALSE)
1898 {
1899 $this->db->trans_rollback();
1900
1901 $this->trigger_events(array('post_update_user', 'post_update_user_unsuccessful'));
1902 $this->set_error('update_unsuccessful');
1903 return FALSE;
1904 }
1905
1906 $this->db->trans_commit();
1907
1908 $this->trigger_events(array('post_update_user', 'post_update_user_successful'));
1909 $this->set_message('update_successful');
1910 return TRUE;
1911 }
1912
1913 /**
1914 * delete_user
1915 *
1916 * @param int|string $id
1917 *
1918 * @return bool
1919 * @author Phil Sturgeon
1920 */
1921 public function delete_user($id)
1922 {
1923 $this->trigger_events('pre_delete_user');
1924
1925 $this->db->trans_begin();
1926
1927 // remove user from groups
1928 $this->remove_from_group(NULL, $id);
1929
1930 // delete user from users table should be placed after remove from group
1931 $this->db->delete($this->tables['users'], array('id' => $id));
1932
1933 if ($this->db->trans_status() === FALSE)
1934 {
1935 $this->db->trans_rollback();
1936 $this->trigger_events(array('post_delete_user', 'post_delete_user_unsuccessful'));
1937 $this->set_error('delete_unsuccessful');
1938 return FALSE;
1939 }
1940
1941 $this->db->trans_commit();
1942
1943 $this->trigger_events(array('post_delete_user', 'post_delete_user_successful'));
1944 $this->set_message('delete_successful');
1945 return TRUE;
1946 }
1947
1948 /**
1949 * update_last_login
1950 *
1951 * @param int|string $id
1952 *
1953 * @return bool
1954 * @author Ben Edmunds
1955 */
1956 public function update_last_login($id)
1957 {
1958 $this->trigger_events('update_last_login');
1959
1960 $this->load->helper('date');
1961
1962 $this->trigger_events('extra_where');
1963
1964 $this->db->update($this->tables['users'], array('last_login' => time()), array('id' => $id));
1965
1966 return $this->db->affected_rows() == 1;
1967 }
1968
1969 /**
1970 * set_lang
1971 *
1972 * @param string $lang
1973 *
1974 * @return bool
1975 * @author Ben Edmunds
1976 */
1977 public function set_lang($lang = 'en')
1978 {
1979 $this->trigger_events('set_lang');
1980
1981 // if the user_expire is set to zero we'll set the expiration two years from now.
1982 if($this->config->item('user_expire', 'ion_auth') === 0)
1983 {
1984 $expire = (60*60*24*365*2);
1985 }
1986 // otherwise use what is set
1987 else
1988 {
1989 $expire = $this->config->item('user_expire', 'ion_auth');
1990 }
1991
1992 set_cookie(array(
1993 'name' => 'lang_code',
1994 'value' => $lang,
1995 'expire' => $expire
1996 ));
1997
1998 return TRUE;
1999 }
2000
2001 /**
2002 * set_session
2003 *
2004 * @param object $user
2005 *
2006 * @return bool
2007 * @author jrmadsen67
2008 */
2009 public function set_session($user)
2010 {
2011 $this->trigger_events('pre_set_session');
2012
2013 $session_data = array(
2014 'identity' => $user->{$this->identity_column},
2015 $this->identity_column => $user->{$this->identity_column},
2016 'email' => $user->email,
2017 'user_id' => $user->id, //everyone likes to overwrite id so we'll use user_id
2018 'old_last_login' => $user->last_login,
2019 'last_check' => time(),
2020 );
2021
2022 $this->session->set_userdata($session_data);
2023
2024 $this->trigger_events('post_set_session');
2025
2026 return TRUE;
2027 }
2028
2029 /**
2030 * remember_user
2031 *
2032 * @param int|string $id
2033 *
2034 * @return bool
2035 * @author Ben Edmunds
2036 */
2037 public function remember_user($id)
2038 {
2039 $this->trigger_events('pre_remember_user');
2040
2041 if (!$id)
2042 {
2043 return FALSE;
2044 }
2045
2046 $user = $this->user($id)->row();
2047
2048 $salt = $this->salt();
2049
2050 $this->db->update($this->tables['users'], array('remember_code' => $salt), array('id' => $id));
2051
2052 if ($this->db->affected_rows() > -1)
2053 {
2054 // if the user_expire is set to zero we'll set the expiration two years from now.
2055 if($this->config->item('user_expire', 'ion_auth') === 0)
2056 {
2057 $expire = (60*60*24*365*2);
2058 }
2059 // otherwise use what is set
2060 else
2061 {
2062 $expire = $this->config->item('user_expire', 'ion_auth');
2063 }
2064
2065 set_cookie(array(
2066 'name' => $this->config->item('identity_cookie_name', 'ion_auth'),
2067 'value' => $user->{$this->identity_column},
2068 'expire' => $expire
2069 ));
2070
2071 set_cookie(array(
2072 'name' => $this->config->item('remember_cookie_name', 'ion_auth'),
2073 'value' => $salt,
2074 'expire' => $expire
2075 ));
2076
2077 $this->trigger_events(array('post_remember_user', 'remember_user_successful'));
2078 return TRUE;
2079 }
2080
2081 $this->trigger_events(array('post_remember_user', 'remember_user_unsuccessful'));
2082 return FALSE;
2083 }
2084
2085 /**
2086 * login_remembed_user
2087 *
2088 * @return bool
2089 * @author Ben Edmunds
2090 */
2091 public function login_remembered_user()
2092 {
2093 $this->trigger_events('pre_login_remembered_user');
2094
2095 // check for valid data
2096 if (!get_cookie($this->config->item('identity_cookie_name', 'ion_auth'))
2097 || !get_cookie($this->config->item('remember_cookie_name', 'ion_auth'))
2098 || !$this->identity_check(get_cookie($this->config->item('identity_cookie_name', 'ion_auth'))))
2099 {
2100 $this->trigger_events(array('post_login_remembered_user', 'post_login_remembered_user_unsuccessful'));
2101 return FALSE;
2102 }
2103
2104 // get the user
2105 $this->trigger_events('extra_where');
2106 $query = $this->db->select($this->identity_column . ', id, email, last_login')
2107 ->where($this->identity_column, urldecode(get_cookie($this->config->item('identity_cookie_name', 'ion_auth'))))
2108 ->where('remember_code', get_cookie($this->config->item('remember_cookie_name', 'ion_auth')))
2109 ->where('active', 1)
2110 ->limit(1)
2111 ->order_by('id', 'desc')
2112 ->get($this->tables['users']);
2113
2114 // if the user was found, sign them in
2115 if ($query->num_rows() == 1)
2116 {
2117 $user = $query->row();
2118
2119 $this->update_last_login($user->id);
2120
2121 $this->set_session($user);
2122
2123 // extend the users cookies if the option is enabled
2124 if ($this->config->item('user_extend_on_login', 'ion_auth'))
2125 {
2126 $this->remember_user($user->id);
2127 }
2128
2129 // Regenerate the session (for security purpose: to avoid session fixation)
2130 $this->_regenerate_session();
2131
2132 $this->trigger_events(array('post_login_remembered_user', 'post_login_remembered_user_successful'));
2133 return TRUE;
2134 }
2135
2136 $this->trigger_events(array('post_login_remembered_user', 'post_login_remembered_user_unsuccessful'));
2137 return FALSE;
2138 }
2139
2140
2141 /**
2142 * create_group
2143 *
2144 * @param string|bool $group_name
2145 * @param string $group_description
2146 * @param array $additional_data
2147 *
2148 * @return int|bool The ID of the inserted group, or FALSE on failure
2149 * @author aditya menon
2150 */
2151 public function create_group($group_name = FALSE, $group_description = '', $additional_data = array())
2152 {
2153 // bail if the group name was not passed
2154 if(!$group_name)
2155 {
2156 $this->set_error('group_name_required');
2157 return FALSE;
2158 }
2159
2160 // bail if the group name already exists
2161 $existing_group = $this->db->get_where($this->tables['groups'], array('name' => $group_name))->num_rows();
2162 if($existing_group !== 0)
2163 {
2164 $this->set_error('group_already_exists');
2165 return FALSE;
2166 }
2167
2168 $data = array('name'=>$group_name,'description'=>$group_description);
2169
2170 // filter out any data passed that doesnt have a matching column in the groups table
2171 // and merge the set group data and the additional data
2172 if (!empty($additional_data)) $data = array_merge($this->_filter_data($this->tables['groups'], $additional_data), $data);
2173
2174 $this->trigger_events('extra_group_set');
2175
2176 // insert the new group
2177 $this->db->insert($this->tables['groups'], $data);
2178 $group_id = $this->db->insert_id($this->tables['groups'] . '_id_seq');
2179
2180 // report success
2181 $this->set_message('group_creation_successful');
2182 // return the brand new group id
2183 return $group_id;
2184 }
2185
2186 /**
2187 * update_group
2188 *
2189 * @param int|string|bool $group_id
2190 * @param string|bool $group_name
2191 * @param string|array $additional_data IMPORTANT! This was string type $description; strings are still allowed
2192 * to maintain backward compatibility. New projects should pass an array of
2193 * data instead.
2194 *
2195 * @return bool
2196 * @author aditya menon
2197 */
2198 public function update_group($group_id = FALSE, $group_name = FALSE, $additional_data = array())
2199 {
2200 if (empty($group_id))
2201 {
2202 return FALSE;
2203 }
2204
2205 $data = array();
2206
2207 if (!empty($group_name))
2208 {
2209 // we are changing the name, so do some checks
2210
2211 // bail if the group name already exists
2212 $existing_group = $this->db->get_where($this->tables['groups'], array('name' => $group_name))->row();
2213 if (isset($existing_group->id) && $existing_group->id != $group_id)
2214 {
2215 $this->set_error('group_already_exists');
2216 return FALSE;
2217 }
2218
2219 $data['name'] = $group_name;
2220 }
2221
2222 // restrict change of name of the admin group
2223 $group = $this->db->get_where($this->tables['groups'], array('id' => $group_id))->row();
2224 if ($this->config->item('admin_group', 'ion_auth') === $group->name && $group_name !== $group->name)
2225 {
2226 $this->set_error('group_name_admin_not_alter');
2227 return FALSE;
2228 }
2229
2230 // TODO Third parameter was string type $description; this following code is to maintain backward compatibility
2231 if (is_string($additional_data))
2232 {
2233 $additional_data = array('description' => $additional_data);
2234 }
2235
2236 // filter out any data passed that doesnt have a matching column in the groups table
2237 // and merge the set group data and the additional data
2238 if (!empty($additional_data))
2239 {
2240 $data = array_merge($this->_filter_data($this->tables['groups'], $additional_data), $data);
2241 }
2242
2243 $this->db->update($this->tables['groups'], $data, array('id' => $group_id));
2244
2245 $this->set_message('group_update_successful');
2246
2247 return TRUE;
2248 }
2249
2250 /**
2251 * delete_group
2252 *
2253 * @param int|string|bool $group_id
2254 *
2255 * @return bool
2256 * @author aditya menon
2257 */
2258 public function delete_group($group_id = FALSE)
2259 {
2260 // bail if mandatory param not set
2261 if(!$group_id || empty($group_id))
2262 {
2263 return FALSE;
2264 }
2265 $group = $this->group($group_id)->row();
2266 if($group->name == $this->config->item('admin_group', 'ion_auth'))
2267 {
2268 $this->trigger_events(array('post_delete_group', 'post_delete_group_notallowed'));
2269 $this->set_error('group_delete_notallowed');
2270 return FALSE;
2271 }
2272
2273 $this->trigger_events('pre_delete_group');
2274
2275 $this->db->trans_begin();
2276
2277 // remove all users from this group
2278 $this->db->delete($this->tables['users_groups'], array($this->join['groups'] => $group_id));
2279 // remove the group itself
2280 $this->db->delete($this->tables['groups'], array('id' => $group_id));
2281
2282 if ($this->db->trans_status() === FALSE)
2283 {
2284 $this->db->trans_rollback();
2285 $this->trigger_events(array('post_delete_group', 'post_delete_group_unsuccessful'));
2286 $this->set_error('group_delete_unsuccessful');
2287 return FALSE;
2288 }
2289
2290 $this->db->trans_commit();
2291
2292 $this->trigger_events(array('post_delete_group', 'post_delete_group_successful'));
2293 $this->set_message('group_delete_successful');
2294 return TRUE;
2295 }
2296
2297 /**
2298 * @param string $event
2299 * @param string $name
2300 * @param string $class
2301 * @param string $method
2302 * @param array $arguments
2303 */
2304 public function set_hook($event, $name, $class, $method, $arguments)
2305 {
2306 $this->_ion_hooks->{$event}[$name] = new stdClass;
2307 $this->_ion_hooks->{$event}[$name]->class = $class;
2308 $this->_ion_hooks->{$event}[$name]->method = $method;
2309 $this->_ion_hooks->{$event}[$name]->arguments = $arguments;
2310 }
2311
2312 /**
2313 * @param string $event
2314 * @param string $name
2315 */
2316 public function remove_hook($event, $name)
2317 {
2318 if (isset($this->_ion_hooks->{$event}[$name]))
2319 {
2320 unset($this->_ion_hooks->{$event}[$name]);
2321 }
2322 }
2323
2324 /**
2325 * @param string $event
2326 */
2327 public function remove_hooks($event)
2328 {
2329 if (isset($this->_ion_hooks->$event))
2330 {
2331 unset($this->_ion_hooks->$event);
2332 }
2333 }
2334
2335 /**
2336 * @param string $event
2337 * @param string $name
2338 *
2339 * @return bool|mixed
2340 */
2341 protected function _call_hook($event, $name)
2342 {
2343 if (isset($this->_ion_hooks->{$event}[$name]) && method_exists($this->_ion_hooks->{$event}[$name]->class, $this->_ion_hooks->{$event}[$name]->method))
2344 {
2345 $hook = $this->_ion_hooks->{$event}[$name];
2346
2347 return call_user_func_array(array($hook->class, $hook->method), $hook->arguments);
2348 }
2349
2350 return FALSE;
2351 }
2352
2353 /**
2354 * @param string|array $events
2355 */
2356 public function trigger_events($events)
2357 {
2358 if (is_array($events) && !empty($events))
2359 {
2360 foreach ($events as $event)
2361 {
2362 $this->trigger_events($event);
2363 }
2364 }
2365 else
2366 {
2367 if (isset($this->_ion_hooks->$events) && !empty($this->_ion_hooks->$events))
2368 {
2369 foreach ($this->_ion_hooks->$events as $name => $hook)
2370 {
2371 $this->_call_hook($events, $name);
2372 }
2373 }
2374 }
2375 }
2376
2377 /**
2378 * set_message_delimiters
2379 *
2380 * Set the message delimiters
2381 *
2382 * @param string $start_delimiter
2383 * @param string $end_delimiter
2384 *
2385 * @return true
2386 * @author Ben Edmunds
2387 */
2388 public function set_message_delimiters($start_delimiter, $end_delimiter)
2389 {
2390 $this->message_start_delimiter = $start_delimiter;
2391 $this->message_end_delimiter = $end_delimiter;
2392
2393 return TRUE;
2394 }
2395
2396 /**
2397 * set_error_delimiters
2398 *
2399 * Set the error delimiters
2400 *
2401 * @param string $start_delimiter
2402 * @param string $end_delimiter
2403 *
2404 * @return true
2405 * @author Ben Edmunds
2406 */
2407 public function set_error_delimiters($start_delimiter, $end_delimiter)
2408 {
2409 $this->error_start_delimiter = $start_delimiter;
2410 $this->error_end_delimiter = $end_delimiter;
2411
2412 return TRUE;
2413 }
2414
2415 /**
2416 * set_message
2417 *
2418 * Set a message
2419 *
2420 * @param string $message The message
2421 *
2422 * @return string The given message
2423 * @author Ben Edmunds
2424 */
2425 public function set_message($message)
2426 {
2427 $this->messages[] = $message;
2428
2429 return $message;
2430 }
2431
2432 /**
2433 * messages
2434 *
2435 * Get the messages
2436 *
2437 * @return string
2438 * @author Ben Edmunds
2439 */
2440 public function messages()
2441 {
2442 $_output = '';
2443 foreach ($this->messages as $message)
2444 {
2445 $messageLang = $this->lang->line($message) ? $this->lang->line($message) : '##' . $message . '##';
2446 $_output .= $this->message_start_delimiter . $messageLang . $this->message_end_delimiter;
2447 }
2448
2449 return $_output;
2450 }
2451
2452 /**
2453 * messages as array
2454 *
2455 * Get the messages as an array
2456 *
2457 * @param bool $langify
2458 *
2459 * @return array
2460 * @author Raul Baldner Junior
2461 */
2462 public function messages_array($langify = TRUE)
2463 {
2464 if ($langify)
2465 {
2466 $_output = array();
2467 foreach ($this->messages as $message)
2468 {
2469 $messageLang = $this->lang->line($message) ? $this->lang->line($message) : '##' . $message . '##';
2470 $_output[] = $this->message_start_delimiter . $messageLang . $this->message_end_delimiter;
2471 }
2472 return $_output;
2473 }
2474 else
2475 {
2476 return $this->messages;
2477 }
2478 }
2479
2480 /**
2481 * clear_messages
2482 *
2483 * Clear messages
2484 *
2485 * @return true
2486 * @author Ben Edmunds
2487 */
2488 public function clear_messages()
2489 {
2490 $this->messages = array();
2491
2492 return TRUE;
2493 }
2494
2495 /**
2496 * set_error
2497 *
2498 * Set an error message
2499 *
2500 * @param string $error The error to set
2501 *
2502 * @return string The given error
2503 * @author Ben Edmunds
2504 */
2505 public function set_error($error)
2506 {
2507 $this->errors[] = $error;
2508
2509 return $error;
2510 }
2511
2512 /**
2513 * errors
2514 *
2515 * Get the error message
2516 *
2517 * @return string
2518 * @author Ben Edmunds
2519 */
2520 public function errors()
2521 {
2522 $_output = '';
2523 foreach ($this->errors as $error)
2524 {
2525 $errorLang = $this->lang->line($error) ? $this->lang->line($error) : '##' . $error . '##';
2526 $_output .= $this->error_start_delimiter . $errorLang . $this->error_end_delimiter;
2527 }
2528
2529 return $_output;
2530 }
2531
2532 /**
2533 * errors as array
2534 *
2535 * Get the error messages as an array
2536 *
2537 * @param bool $langify
2538 *
2539 * @return array
2540 * @author Raul Baldner Junior
2541 */
2542 public function errors_array($langify = TRUE)
2543 {
2544 if ($langify)
2545 {
2546 $_output = array();
2547 foreach ($this->errors as $error)
2548 {
2549 $errorLang = $this->lang->line($error) ? $this->lang->line($error) : '##' . $error . '##';
2550 $_output[] = $this->error_start_delimiter . $errorLang . $this->error_end_delimiter;
2551 }
2552 return $_output;
2553 }
2554 else
2555 {
2556 return $this->errors;
2557 }
2558 }
2559
2560 /**
2561 * clear_errors
2562 *
2563 * Clear Errors
2564 *
2565 * @return true
2566 * @author Ben Edmunds
2567 */
2568 public function clear_errors()
2569 {
2570 $this->errors = array();
2571
2572 return TRUE;
2573 }
2574
2575 /**
2576 * @param string $table
2577 * @param array $data
2578 *
2579 * @return array
2580 */
2581 protected function _filter_data($table, $data)
2582 {
2583 $filtered_data = array();
2584 $columns = $this->db->list_fields($table);
2585
2586 if (is_array($data))
2587 {
2588 foreach ($columns as $column)
2589 {
2590 if (array_key_exists($column, $data))
2591 $filtered_data[$column] = $data[$column];
2592 }
2593 }
2594
2595 return $filtered_data;
2596 }
2597
2598 /**
2599 * @deprecated Now just returns the given string for backwards compatibility reasons
2600 * @param string $ip_address The IP address
2601 *
2602 * @return string The given IP address
2603 */
2604 protected function _prepare_ip($ip_address) {
2605 return $ip_address;
2606 }
2607
2608 /**
2609 * Regenerate the session without losing any data
2610 *
2611 */
2612 protected function _regenerate_session() {
2613
2614 if (substr(CI_VERSION, 0, 1) == '2')
2615 {
2616 // Save sess_time_to_update and set it temporarily to 0
2617 // This is done in order to forces the sess_update method to regenerate
2618 $old_sess_time_to_update = $this->session->sess_time_to_update;
2619 $this->session->sess_time_to_update = 0;
2620
2621 // Call the sess_update method to actually regenerate the session ID
2622 $this->session->sess_update();
2623
2624 // Restore sess_time_to_update
2625 $this->session->sess_time_to_update = $old_sess_time_to_update;
2626 }
2627 else
2628 {
2629 $this->session->sess_regenerate(FALSE);
2630 }
2631 }
2632}