GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — develop (#145)
by
unknown
09:16 queued 03:08
created

LocalAuthentication::isThrottled()   D

Complexity

Conditions 13
Paths 31

Size

Total Lines 98
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 98
rs 4.9923
cc 13
eloc 33
nc 31
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php namespace Myth\Auth;
2
/**
3
 * Sprint
4
 *
5
 * A set of power tools to enhance the CodeIgniter framework and provide consistent workflow.
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in
15
 * all copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
 * THE SOFTWARE.
24
 *
25
 * @package     Sprint
26
 * @author      Lonnie Ezell
27
 * @copyright   Copyright 2014-2015, New Myth Media, LLC (http://newmythmedia.com)
28
 * @license     http://opensource.org/licenses/MIT  (MIT)
29
 * @link        http://sprintphp.com
30
 * @since       Version 1.0
31
 */
32
33
use Myth\Auth\AuthenticateInterface;
34
use Myth\Events\Events;
35
36
/**
37
 * Class LocalAuthentication
38
 *
39
 * Provides most of the Authentication that web applications would need,
40
 * at least as far as local authentication goes. It does NOT provide
41
 * social authentication through third-party applications.
42
 *
43
 * The system attempts to incorporate as many of the ideas and best practices
44
 * set forth in the following documents:
45
 *
46
 *  - http://stackoverflow.com/questions/549/the-definitive-guide-to-form-based-website-authentication
47
 *  - https://www.owasp.org/index.php/Guide_to_Authentication
48
 *
49
 * todo: Set the error string for all error states here.
50
 *
51
 * @package Myth\Auth
52
 */
53
class LocalAuthentication implements AuthenticateInterface {
54
55
    protected $ci;
56
57
    protected $user = null;
58
59
    public $user_model = null;
60
61
    public $error = null;
62
63
    //--------------------------------------------------------------------
64
65
    public function __construct( $ci=null )
66
    {
67
        if ($ci)
68
        {
69
            $this->ci= $ci;
70
        }
71
        else
72
        {
73
            $this->ci =& get_instance();
74
        }
75
76
        // Get our compatibility password file loaded up.
77
        if (! function_exists('password_hash'))
78
        {
79
            require_once dirname(__FILE__) .'password.php';
80
        }
81
82
        if (empty($this->ci->session))
83
        {
84
            $this->ci->load->library('session');
85
        }
86
87
        $this->ci->config->load('auth');
88
        $this->ci->load->model('auth/login_model');
89
        $this->ci->load->language('auth/auth');
90
    }
91
92
    //--------------------------------------------------------------------
93
94
    /**
95
     * Attempt to log a user into the system.
96
     *
97
     * $credentials is an array of key/value pairs needed to log the user in.
98
     * This is often email/password, or username/password.
99
     *
100
     * @param array $credentials
101
     * @param bool  $remember
102
     * @return bool|mixed
103
     */
104
    public function login($credentials, $remember=false)
105
    {
106
        $user = $this->validate($credentials, true);
107
108
        if (! $user)
109
        {
110
	        // We need to send an error even if no
111
	        // user was found!
112
	        $this->error = lang('auth.invalid_user');
113
114
	        // If an email is present, log the attempt
115
	        if (! empty($credentials['email']) )
116
	        {
117
		        $this->ci->login_model->recordLoginAttempt($credentials['email']);
118
	        }
119
120
            $this->user = null;
121
            return $user;
122
        }
123
124
        // If the user is throttled due to too many invalid logins
125
        // or the system is under attack, kick them back.
126
        // We need to test for this after validation because we
127
        // don't want it to affect a valid login.
128
129
        // If throttling time is above zero, we can't allow
130
        // logins now.
131
        $time = (int)$this->isThrottled($user['email']);
132
        if ($time > 0)
133
        {
134
            $this->error = sprintf(lang('auth.throttled'), $time);
135
            return false;
136
        }
137
138
        $this->loginUser($user);
139
140
        if ($remember)
141
        {
142
            $this->rememberUser($user);
143
        }
144
145
        Events::trigger('didLogin', [$user]);
146
147
        return true;
148
    }
149
150
    //--------------------------------------------------------------------
151
152
    /**
153
     * Validates user login information without logging them in.
154
     *
155
     * $credentials is an array of key/value pairs needed to log the user in.
156
     * This is often email/password, or username/password.
157
     *
158
     * @param $credentials
159
     * @param bool $return_user
160
     * @return mixed
161
     */
162
    public function validate($credentials, $return_user=false)
163
    {
164
        // Can't validate without a password.
165
        if (empty($credentials['password']) || count($credentials) < 2)
166
        {
167
            return null;
168
        }
169
170
        $password = $credentials['password'];
171
        unset($credentials['password']);
172
173
	    // We should only be allowed 1 single other credential to
174
	    // test against.
175
	    if (count($credentials) > 1)
176
	    {
177
		    $this->error = lang('auth.too_many_credentials');
178
		    return false;
179
	    }
180
181
        // Ensure that the fields are allowed validation fields
182
	    if (! in_array(key($credentials), config_item('auth.valid_fields')) )
183
	    {
184
		    $this->error = lang('auth.invalid_credentials');
185
		    return false;
186
	    }
187
188
        // We do not want to force case-sensitivity on things
189
        // like username and email for usability sake.
190
        if (! empty($credentials['email']))
191
        {
192
            $credentials['email'] = strtolower($credentials['email']);
193
        }
194
195
        // Can we find a user with those credentials?
196
        $user = $this->user_model->as_array()
197
                                 ->where($credentials)
198
                                 ->first();
199
200
        if (! $user)
201
        {
202
            $this->error = lang('auth.invalid_user');
203
            return false;
204
        }
205
206
        // Now, try matching the passwords.
207
        $result =  password_verify($password, $user['password_hash']);
208
209
        if (! $result)
210
        {
211
            $this->error = lang('auth.invalid_password');
212
            $this->ci->login_model->recordLoginAttempt($user['email']);
213
            return false;
214
        }
215
216
        // Check to see if the password needs to be rehashed.
217
        // This would be due to the hash algorithm or hash
218
        // cost changing since the last time that a user
219
        // logged in.
220
        if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT, ['cost' => config_item('auth.hash_cost')] ))
221
        {
222
            $new_hash = Password::hashPassword($password);
223
            $this->user_model->skip_validation()
224
                             ->update($user['id'], ['password_hash' => $new_hash]);
225
            unset($new_hash);
226
        }
227
228
        // Is the user active?
229
        if (! $user['active'])
230
        {
231
            $this->error = lang('auth.inactive_account');
232
            return false;
233
        }
234
235
        return $return_user ? $user : true;
236
    }
237
238
    //--------------------------------------------------------------------
239
240
    /**
241
     * Logs a user out and removes all session information.
242
     *
243
     * @return mixed
244
     */
245
    public function logout()
246
    {
247
        $this->ci->load->helper('cookie');
248
249
        if (! Events::trigger('beforeLogout', [$this->user]))
250
        {
251
            return false;
252
        }
253
254
        // Destroy the session data - but ensure a session is still
255
        // available for flash messages, etc.
256
        if (isset($_SESSION))
257
        {
258
            foreach ( $_SESSION as $key => $value )
259
            {
260
                $_SESSION[ $key ] = NULL;
261
                unset( $_SESSION[ $key ] );
262
            }
263
        }
264
        // Also, regenerate the session ID for a touch of added safety.
265
        $this->ci->session->sess_regenerate(true);
266
267
        // Take care of any rememberme functionality.
268
        if (config_item('auth.allow_remembering'))
269
        {
270
            $token = get_cookie('remember');
271
272
            $this->invalidateRememberCookie($this->user['email'], $token);
273
        }
274
    }
275
276
    //--------------------------------------------------------------------
277
278
    /**
279
     * Checks whether a user is logged in or not.
280
     *
281
     * @return bool
282
     */
283
    public function isLoggedIn()
284
    {
285
        $id = $this->ci->session->userdata('logged_in');
286
287
        if (! $id)
288
        {
289
            return false;
290
        }
291
292
        // If the user var hasn't been filled in, we need to fill it in,
293
        // since this method will typically be used as the only method
294
        // to determine whether a user is logged in or not.
295
        if (! $this->user)
296
        {
297
            $this->user = $this->user_model->as_array()
298
                                           ->find_by('id', (int)$id);
299
300
            if (empty($this->user))
301
            {
302
                return false;
303
            }
304
        }
305
306
        // If logged in, ensure cache control
307
        // headers are in place
308
        $this->setHeaders();
309
310
        return true;
311
    }
312
313
    //--------------------------------------------------------------------
314
315
    /**
316
     * Attempts to log a user in based on the "remember me" cookie.
317
     *
318
     * @return bool
319
     */
320
    public function viaRemember()
321
    {
322
        if (! config_item('auth.allow_remembering'))
323
        {
324
            return false;
325
        }
326
327
        $this->ci->load->helper('cookie');
328
329
        if (! $token = get_cookie('remember'))
330
        {
331
            return false;
332
        }
333
334
        // Attempt to match the token against our auth_tokens table.
335
        $query = $this->ci->db->where('hash', $this->ci->login_model->hashRememberToken($token))
336
                              ->get('auth_tokens');
337
338
        if (! $query->num_rows())
339
        {
340
            return false;
341
        }
342
343
        // Grab the user
344
        $email = $query->row()->email;
345
346
        $user = $this->user_model->as_array()
347
                                 ->find_by('email', $email);
348
349
        $this->loginUser($user);
350
351
        // We only want our remember me tokens to be valid
352
        // for a single use.
353
        $this->refreshRememberCookie($user, $token);
354
355
        return true;
356
    }
357
358
    //--------------------------------------------------------------------
359
360
    /**
361
     * Registers a new user and handles activation method.
362
     *
363
     * @param $user_data
364
     * @return bool
365
     */
366
    public function registerUser($user_data)
367
    {
368
        // Anything special needed for Activation?
369
        $method = config_item('auth.activation_method');
370
371
        $user_data['active'] = $method == 'auto' ? 1 : 0;
372
373
        // If via email, we need to generate a hash
374
        $this->ci->load->helper('string');
375
        $token = random_string('alnum', 24);
376
        $user_data['activate_hash'] = hash('sha1', config_item('auth.salt') . $token);
377
378
        // Email should NOT be case sensitive.
379
        if (! empty($user_data['email']))
380
        {
381
            $user_data['email'] = strtolower($user_data['email']);
382
        }
383
384
        // Save the user
385
        if (! $id = $this->user_model->insert($user_data))
386
        {
387
            $this->error = $this->user_model->error();
388
            return false;
389
        }
390
391
        $data = [
392
            'user_id' => $id,
393
            'email'   => $user_data['email'],
394
            'token'   => $token,
395
            'method'  => $method
396
        ];
397
398
        Events::trigger('didRegisterUser', [$data]);
399
400
        return true;
401
    }
402
403
    //--------------------------------------------------------------------
404
405
    /**
406
     * Used to verify the user values and activate a user so they can
407
     * visit the site.
408
     *
409
     * @param $data
410
     * @return bool
411
     */
412
    public function activateUser($data)
413
    {
414
        $post = [
415
            'email'         => $data['email'],
416
            'activate_hash' => hash('sha1', config_item('auth.salt') . $data['code'])
417
        ];
418
419
        $user = $this->user_model->where($post)
420
                                 ->first();
421
422
        if (! $user) {
423
            $this->error = $this->user_model->error() ? $this->user_model->error() : lang('auth.activate_no_user');
424
425
            return false;
426
        }
427
428 View Code Duplication
        if (! $this->user_model->update($user->id, ['active' => 1, 'activate_hash' => null]))
429
        {
430
            $this->error = $this->user_model->error();
431
            return false;
432
        }
433
434
        Events::trigger('didActivate', [(array)$user]);
435
436
        return true;
437
    }
438
439
    //--------------------------------------------------------------------
440
441
    /**
442
     * Used to allow manual activation of a user with a known ID.
443
     *
444
     * @param $id
445
     * @return bool
446
     */
447
    public function activateUserById($id)
448
    {
449 View Code Duplication
        if (! $this->user_model->update($id, ['active' => 1, 'activate_hash' => null]))
450
        {
451
            $this->error = $this->user_model->error();
452
            return false;
453
        }
454
455
        Events::trigger('didActivate', [$this->user_model->as_array()->find($id)]);
456
457
        return true;
458
    }
459
460
    //--------------------------------------------------------------------
461
462
    /**
463
     * Grabs the current user object. Returns NULL if nothing found.
464
     *
465
     * @return array|null
466
     */
467
    public function user()
468
    {
469
        return $this->user;
470
    }
471
472
    //--------------------------------------------------------------------
473
474
    /**
475
     * A convenience method to grab the current user's ID.
476
     *
477
     * @return int|null
478
     */
479
    public function id()
480
    {
481
        if (! is_array($this->user) || empty($this->user['id']))
482
        {
483
            return null;
484
        }
485
486
        return (int)$this->user['id'];
487
    }
488
489
    //--------------------------------------------------------------------
490
491
    /**
492
     * Checks to see if the user is currently being throttled.
493
     *
494
     *  - If they are NOT, will return FALSE.
495
     *  - If they ARE, will return the number of seconds until they can try again.
496
     *
497
     * @param $email
498
     * @return mixed
499
     */
500
    public function isThrottled($email)
501
    {
502
        // Not throttling? Get outta here!
503
        if (! config_item('auth.allow_throttling'))
504
        {
505
            return false;
506
        }
507
508
        // Emails should NOT be case sensitive.
509
        $email = strtolower($email);
510
511
        // Have any attempts been made?
512
        $attempts = $this->ci->login_model->countLoginAttempts($email);
513
514
        // Grab the amount of time to add if the system thinks we're
515
        // under a distributed brute force attack.
516
        // Affect users that have at least 1 failure login attempt
517
        $dbrute_time = ($attempts === 0) ? 0 : $this->ci->login_model->distributedBruteForceTime();
518
519
        // If this user was found to possibly be under a brute
520
        // force attack, their account would have been banned
521
        // for 15 minutes.
522
        if ($time = isset($_SESSION['bruteBan']) ? $_SESSION['bruteBan'] : false)
523
        {
524
            // If the current time is less than the
525
            // the ban expiration, plus any distributed time
526
            // then the user can't login just yet.
527
            if ($time + $dbrute_time > time())
528
            {
529
                // The user is banned still...
530
                $this->error = lang('auth.bruteBan_notice');
531
                return ($time + $dbrute_time) - time();
532
            }
533
534
            // Still here? The the ban time is over...
535
            unset($_SESSION['bruteBan']);
536
        }
537
538
        // Grab the time of last attempt and
539
        // determine if we're throttled by amount of time passed.
540
        $last_time = $this->ci->login_model->lastLoginAttemptTime($email);
541
542
        $allowed = config_item('auth.allowed_login_attempts');
543
544
        // We're not throttling if there are 0 attempts or
545
        // the number is less than or equal to the allowed free attempts
546
        if ($attempts === 0 || $attempts <= $allowed)
547
        {
548
            // Before we can say there's nothing up here,
549
            // we need to check dbrute time.
550
            $time_left = $last_time + $dbrute_time - time();
551
552
            if ($time_left > 0)
553
            {
554
                return $time_left;
555
            }
556
557
            return false;
558
        }
559
560
        // If the number of attempts is excessive (above 100) we need
561
        // to check the elapsed time of all of these attacks. If they are
562
        // less than 1 minute it's obvious this is a brute force attack,
563
        // so we'll set a session flag and block that user for 15 minutes.
564
        if ($attempts > 100 && $this->ci->login_model->isBruteForced($email))
565
        {
566
            $this->error = lang('auth.bruteBan_notice');
567
568
            $ban_time = 60 * 15;    // 15 minutes
569
            $_SESSION['bruteBan'] = time() + $ban_time;
570
            return $ban_time;
571
        }
572
573
        // Get our allowed attempts out of the picture.
574
        $attempts = $attempts - $allowed;
575
576
        $max_time = config_item('auth.max_throttle_time');
577
578
        $add_time = pow(5, $attempts);
579
580
        if ($add_time > $max_time)
581
        {
582
            $add_time = $max_time;
583
        }
584
585
        $next_time = $last_time + $add_time + $dbrute_time;
586
587
        $current = time();
588
589
        // We are NOT throttled if we are already
590
        // past the allowed time.
591
        if ($current > $next_time)
592
        {
593
            return false;
594
        }
595
596
        return $next_time - $current;
597
    }
598
599
    //--------------------------------------------------------------------
600
601
    /**
602
     * Sends a password reset link email to the user associated with
603
     * the passed in $email.
604
     *
605
     * @param $email
606
     * @return mixed
607
     */
608
    public function remindUser($email)
609
    {
610
        // Emails should NOT be case sensitive.
611
        $email = strtolower($email);
612
613
        // Is it a valid user?
614
        $user = $this->user_model->find_by('email', $email);
615
616
        if (! $user)
617
        {
618
            $this->error = lang('auth.invalid_email');
619
            return false;
620
        }
621
622
        // Generate/store our codes
623
        $this->ci->load->helper('string');
624
        $token = random_string('alnum', 24);
625
        $hash = hash('sha1', config_item('auth.salt') .$token);
626
627
        $result = $this->user_model->update($user->id, ['reset_hash' => $hash]);
628
629
        if (! $result)
630
        {
631
            $this->error = $this->user_model->error();
632
            return false;
633
        }
634
635
        Events::trigger('didRemindUser', [(array)$user, $token]);
636
637
        return true;
638
    }
639
640
    //--------------------------------------------------------------------
641
642
    /**
643
     * Validates the credentials provided and, if valid, resets the password.
644
     *
645
     * The $credentials array MUST contain a 'code' key with the string to
646
     * hash and check against the reset_hash.
647
     *
648
     * @param $credentials
649
     * @param $password
650
     * @param $passConfirm
651
     * @return mixed
652
     */
653
    public function resetPassword($credentials, $password, $passConfirm)
654
    {
655
        if (empty($credentials['code']))
656
        {
657
            $this->error = lang('auth.need_reset_code');
658
            return false;
659
        }
660
661
        // Generate a hash to match against the table.
662
        $credentials['reset_hash'] = hash('sha1', config_item('auth.salt') .$credentials['code']);
663
        unset($credentials['code']);
664
665
        if (! empty($credentials['email']))
666
        {
667
            $credentials['email'] = strtolower($credentials['email']);
668
        }
669
670
        // Is there a matching user?
671
        $user = $this->user_model->find_by($credentials);
672
673
        if (! $user)
674
        {
675
            $this->error = lang('auth.reset_no_user');
676
            return false;
677
        }
678
679
        // Update their password and reset their reset_hash
680
        $data = [
681
            'password'     => $password,
682
            'pass_confirm' => $passConfirm,
683
            'reset_hash'   => null
684
        ];
685
686
        if (! $this->user_model->update($user->id, $data))
687
        {
688
            $this->error = $this->user_model->error();
689
            return false;
690
        }
691
692
        Events::trigger('didResetPassword', [(array)$user]);
693
694
        return true;
695
    }
696
697
    //--------------------------------------------------------------------
698
699
    /**
700
     * Provides a way for implementations to allow new statuses to be set
701
     * on the user. The details will vary based upon implementation, but
702
     * will often allow for banning or suspending users.
703
     *
704
     * @param $newStatus
705
     * @param null $message
706
     * @return mixed
707
     */
708
    public function changeStatus($newStatus, $message=null)
709
    {
710
        // todo actually record new users status!
711
    }
712
713
    //--------------------------------------------------------------------
714
715
    /**
716
     * Allows the consuming application to pass in a reference to the
717
     * model that should be used.
718
     *
719
     * The model MUST extend Myth\Models\CIDbModel.
720
     *
721
     * @param $model
722
     * @param bool $allow_any_parent
723
     * @return mixed
724
     */
725
    public function useModel($model, $allow_any_parent=false)
726
    {
727
        if (! $allow_any_parent && get_parent_class($model) != 'Myth\Models\CIDbModel')
728
        {
729
            throw new \RuntimeException('Models passed into LocalAuthenticate MUST extend Myth\Models\CIDbModel');
730
        }
731
732
        $this->user_model =& $model;
733
734
        return $this;
735
    }
736
737
    //--------------------------------------------------------------------
738
739
    public function error()
740
    {
741
        if (validation_errors())
742
        {
743
            return validation_errors();
744
        }
745
746
        return $this->error;
747
    }
748
749
    //--------------------------------------------------------------------
750
751
    //--------------------------------------------------------------------
752
    // Login Records
753
    //--------------------------------------------------------------------
754
755
    /**
756
     * Purges all login attempt records from the database.
757
     *
758
     * @param $email
759
     */
760 View Code Duplication
    public function purgeLoginAttempts($email)
761
    {
762
        // Emails should NOT be case sensitive.
763
        $email = strtolower($email);
764
765
        $this->ci->login_model->purgeLoginAttempts($email);
766
767
        // @todo record activity of login attempts purge.
768
        Events::trigger('didPurgeLoginAttempts', [$email]);
769
    }
770
771
    //--------------------------------------------------------------------
772
773
    /**
774
     * Purges all remember tokens for a single user. Effectively logs
775
     * a user out of all devices. Intended to allow users to log themselves
776
     * out of all devices as a security measure.
777
     *
778
     * @param $email
779
     */
780 View Code Duplication
    public function purgeRememberTokens($email)
781
    {
782
        // Emails should NOT be case sensitive.
783
        $email = strtolower($email);
784
785
        $this->ci->login_model->purgeRememberTokens($email);
786
787
        // todo record activity of remember me purges.
788
        Events::trigger('didPurgeRememberTokens', [$email]);
789
    }
790
791
    //--------------------------------------------------------------------
792
793
    //--------------------------------------------------------------------
794
    // Protected Methods
795
    //--------------------------------------------------------------------
796
797
    /**
798
     * Check if Allow Persistent Login Cookies is enable
799
     *
800
     * @param $user
801
     */
802
    protected function rememberUser($user)
803
    {
804
        if (! config_item('auth.allow_remembering'))
805
        {
806
            log_message('debug', 'Auth library set to refuse "Remember Me" functionality.');
807
            return false;
808
        }
809
810
        $this->refreshRememberCookie($user);
811
    }
812
813
    //--------------------------------------------------------------------
814
815
    /**
816
     * Invalidates the current rememberme cookie/database entry, creates
817
     * a new one, stores it and returns the new value.
818
     *
819
     * @param $user
820
     * @param null $token
821
     * @return mixed
822
     */
823
    protected function refreshRememberCookie($user, $token=null)
824
    {
825
        $this->ci->load->helper('cookie');
826
827
        // If a token is passed in, we know we're removing the
828
        // old one.
829
        if (! empty($token))
830
        {
831
            $this->invalidateRememberCookie($user['email'], $token);
832
        }
833
834
        $new_token = $this->ci->login_model->generateRememberToken($user);
835
836
        // Save the token to the database.
837
        $data = [
838
            'email'   => $user['email'],
839
            'hash'    => sha1(config_item('auth.salt') . $new_token),
840
            'created' => date('Y-m-d H:i:s')
841
        ];
842
843
        $this->ci->db->insert('auth_tokens', $data);
844
845
        // Create the cookie
846
        set_cookie(
847
            'remember',                             // Cookie Name
848
            $new_token,                             // Value
849
            config_item('auth.remember_length'),    // # Seconds until it expires
850
            config_item('cookie_domain'),
851
            config_item('cookie_path'),
852
            config_item('cookie_prefix'),
853
            false,                                  // Only send over HTTPS?
854
            true                                    // Hide from Javascript?
855
        );
856
857
        return $new_token;
858
    }
859
860
    //--------------------------------------------------------------------
861
862
    /**
863
     * Deletes any current remember me cookies and database entries.
864
     *
865
     * @param $email
866
     * @param $token
867
     * @return string The new token (not the hash).
868
     */
869
    protected function invalidateRememberCookie($email, $token)
870
    {
871
        // Emails should NOT be case sensitive.
872
        $email = strtolower($email);
873
874
        // Remove from the database
875
        $this->ci->login_model->deleteRememberToken($email, $token);
876
877
        // Remove the cookie
878
        delete_cookie(
879
            'remember',
880
            config_item('cookie_domain'),
881
            config_item('cookie_path'),
882
            config_item('cookie_prefix')
883
        );
884
    }
885
886
    //--------------------------------------------------------------------
887
888
    /**
889
     * Handles the nitty gritty of actually logging our user into the system.
890
     * Does NOT perform the authentication, just sets the system up so that
891
     * it knows we're here.
892
     *
893
     * @param $user
894
     */
895
    protected function loginUser($user)
896
    {
897
        // Save the user for later access
898
        $this->user = $user;
899
900
        // Regenerate the session ID to help protect
901
        // against session fixation
902
        $this->ci->session->sess_regenerate();
903
904
        // Let the session know that we're logged in.
905
        $this->ci->session->set_userdata('logged_in', $user['id']);
906
907
        // Clear our login attempts
908
        $this->ci->login_model->purgeLoginAttempts($user['email']);
909
910
        // Record a new Login
911
        $this->ci->login_model->recordLogin($user);
912
913
        // If logged in, ensure cache control
914
        // headers are in place
915
        $this->setHeaders();
916
917
        // We'll give a 20% chance to need to do a purge since we
918
        // don't need to purge THAT often, it's just a maintenance issue.
919
        // to keep the table from getting out of control.
920
        if (mt_rand(1, 100) < 20)
921
        {
922
            $this->ci->login_model->purgeOldRememberTokens();
923
        }
924
    }
925
926
    //--------------------------------------------------------------------
927
928
    /**
929
     * Sets the headers to ensure that pages are not cached when a user
930
     * is logged in, helping to protect against logging out and then
931
     * simply hitting the Back button on the browser and getting private
932
     * information because the page was loaded from cache.
933
     */
934
    protected function setHeaders()
935
    {
936
        $this->ci->output->set_header('Cache-Control: no-store, no-cache, must-revalidate');
937
        $this->ci->output->set_header('Cache-Control: post-check=0, pre-check=0');
938
        $this->ci->output->set_header('Pragma: no-cache');
939
    }
940
941
    //--------------------------------------------------------------------
942
943
944
}
945