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
Push — develop ( ce69c9...06e1bf )
by Lonnie
06:20
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 the user is throttled due to too many invalid logins
109
        // or the system is under attack, kick them back.
110
        // We need to test for this after validation because we
111
        // don't want it to affect a valid login.
112
113
        // If there is user or email field available
114
        if ($user || ! empty($credentials['email']))
115
        {
116
        	// Set email to check
117
            $email = $user ? $user['email'] : $credentials['email'];
118
            // If throttling time is above zero, we can't allow
119
        	// logins now.
120
            $time = (int)$this->isThrottled($email);
121
            if ($time > 0)
122
            {
123
                $this->error = sprintf(lang('auth.throttled'), $time);
124
                return false;
125
            }
126
        }
127
128
        if (! $user)
129
        {
130
        	if (empty($this->error))
131
            {
132
                // We need to set an error if there is no one
133
                $this->error = lang('auth.invalid_user');
134
            }
135
            $this->user = null;
136
            return $user;
137
        }       
138
139
        $this->loginUser($user);
140
141
        if ($remember)
142
        {
143
            $this->rememberUser($user);
144
        }
145
146
        Events::trigger('didLogin', [$user]);
147
148
        return true;
149
    }
150
151
    //--------------------------------------------------------------------
152
153
    /**
154
     * Validates user login information without logging them in.
155
     *
156
     * $credentials is an array of key/value pairs needed to log the user in.
157
     * This is often email/password, or username/password.
158
     *
159
     * @param $credentials
160
     * @param bool $return_user
161
     * @return mixed
162
     */
163
    public function validate($credentials, $return_user=false)
164
    {
165
        // We do not want to force case-sensitivity on things
166
        // like username and email for usability sake.
167
        if (! empty($credentials['email']))
168
        {
169
            $credentials['email'] = strtolower($credentials['email']);
170
        }
171
172
        // Can't validate without a password.
173
        if (empty($credentials['password']) || count($credentials) < 2)
174
        {
175
        	// If an email is present, log the attempt
176
            if (! empty($credentials['email']))
177
            {
178
                $this->ci->login_model->recordLoginAttempt($credentials['email']);
179
            }
180
            return null;
181
        }
182
183
        $password = $credentials['password'];
184
        unset($credentials['password']);
185
186
        // We should only be allowed 1 single other credential to
187
        // test against.
188 View Code Duplication
        if (count($credentials) > 1)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
189
        {
190
            $this->error = lang('auth.too_many_credentials');
191
            // If an email is present, log the attempt
192
            if (! empty($credentials['email']))
193
            {
194
                $this->ci->login_model->recordLoginAttempt($credentials['email']);
195
            }
196
            return false;
197
        }
198
199
        // Ensure that the fields are allowed validation fields
200
        if (! in_array(key($credentials), config_item('auth.valid_fields')) )
201
        {
202
            $this->error = lang('auth.invalid_credentials');
203
            // If an email is present, log the attempt
204
            if (! empty($credentials['email']))
205
            {
206
                $this->ci->login_model->recordLoginAttempt($credentials['email']);
207
            }
208
            return false;
209
        }
210
211
        // Can we find a user with those credentials?
212
        $user = $this->user_model->as_array()
213
                                 ->where($credentials)
214
                                 ->first();
215
216 View Code Duplication
        if (! $user)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
217
        {
218
            $this->error = lang('auth.invalid_user');
219
            // If an email is present, log the attempt
220
            if (! empty($credentials['email']))
221
            {
222
                $this->ci->login_model->recordLoginAttempt($credentials['email']);
223
            }
224
            return false;
225
        }
226
227
        // Now, try matching the passwords.
228
        $result =  password_verify($password, $user['password_hash']);
229
230
        if (! $result)
231
        {
232
            $this->error = lang('auth.invalid_password');
233
            $this->ci->login_model->recordLoginAttempt($user['email']);
234
            return false;
235
        }
236
237
        // Check to see if the password needs to be rehashed.
238
        // This would be due to the hash algorithm or hash
239
        // cost changing since the last time that a user
240
        // logged in.
241
        if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT, ['cost' => config_item('auth.hash_cost')] ))
242
        {
243
            $new_hash = Password::hashPassword($password);
244
            $this->user_model->skip_validation()
245
                             ->update($user['id'], ['password_hash' => $new_hash]);
246
            unset($new_hash);
247
        }
248
249
        // Is the user active?
250
        if (! $user['active'])
251
        {
252
            $this->error = lang('auth.inactive_account');
253
            return false;
254
        }
255
256
        return $return_user ? $user : true;
257
    }
258
259
    //--------------------------------------------------------------------
260
261
    /**
262
     * Logs a user out and removes all session information.
263
     *
264
     * @return mixed
265
     */
266
    public function logout()
267
    {
268
        $this->ci->load->helper('cookie');
269
270
        if (! Events::trigger('beforeLogout', [$this->user]))
271
        {
272
            return false;
273
        }
274
275
        // Destroy the session data - but ensure a session is still
276
        // available for flash messages, etc.
277
        if (isset($_SESSION))
278
        {
279
            foreach ( $_SESSION as $key => $value )
280
            {
281
                $_SESSION[ $key ] = NULL;
282
                unset( $_SESSION[ $key ] );
283
            }
284
        }
285
        // Also, regenerate the session ID for a touch of added safety.
286
        $this->ci->session->sess_regenerate(true);
287
288
        // Take care of any rememberme functionality.
289
        if (config_item('auth.allow_remembering'))
290
        {
291
            $token = get_cookie('remember');
292
293
            $this->invalidateRememberCookie($this->user['email'], $token);
294
        }
295
    }
296
297
    //--------------------------------------------------------------------
298
299
    /**
300
     * Checks whether a user is logged in or not.
301
     *
302
     * @return bool
303
     */
304
    public function isLoggedIn()
305
    {
306
        $id = $this->ci->session->userdata('logged_in');
307
308
        if (! $id)
309
        {
310
            return false;
311
        }
312
313
        // If the user var hasn't been filled in, we need to fill it in,
314
        // since this method will typically be used as the only method
315
        // to determine whether a user is logged in or not.
316
        if (! $this->user)
317
        {
318
            $this->user = $this->user_model->as_array()
319
                                           ->find_by('id', (int)$id);
320
321
            if (empty($this->user))
322
            {
323
                return false;
324
            }
325
        }
326
327
        // If logged in, ensure cache control
328
        // headers are in place
329
        $this->setHeaders();
330
331
        return true;
332
    }
333
334
    //--------------------------------------------------------------------
335
336
    /**
337
     * Attempts to log a user in based on the "remember me" cookie.
338
     *
339
     * @return bool
340
     */
341
    public function viaRemember()
342
    {
343
        if (! config_item('auth.allow_remembering'))
344
        {
345
            return false;
346
        }
347
348
        $this->ci->load->helper('cookie');
349
350
        if (! $token = get_cookie('remember'))
351
        {
352
            return false;
353
        }
354
355
        // Attempt to match the token against our auth_tokens table.
356
        $query = $this->ci->db->where('hash', $this->ci->login_model->hashRememberToken($token))
357
                              ->get('auth_tokens');
358
359
        if (! $query->num_rows())
360
        {
361
            return false;
362
        }
363
364
        // Grab the user
365
        $email = $query->row()->email;
366
367
        $user = $this->user_model->as_array()
368
                                 ->find_by('email', $email);
369
370
        $this->loginUser($user);
371
372
        // We only want our remember me tokens to be valid
373
        // for a single use.
374
        $this->refreshRememberCookie($user, $token);
375
376
        return true;
377
    }
378
379
    //--------------------------------------------------------------------
380
381
    /**
382
     * Registers a new user and handles activation method.
383
     *
384
     * @param $user_data
385
     * @return bool
386
     */
387
    public function registerUser($user_data)
388
    {
389
        // Anything special needed for Activation?
390
        $method = config_item('auth.activation_method');
391
392
        $user_data['active'] = $method == 'auto' ? 1 : 0;
393
394
        // If via email, we need to generate a hash
395
        $this->ci->load->helper('string');
396
        $token = random_string('alnum', 24);
397
        $user_data['activate_hash'] = hash('sha1', config_item('auth.salt') . $token);
398
399
        // Email should NOT be case sensitive.
400
        if (! empty($user_data['email']))
401
        {
402
            $user_data['email'] = strtolower($user_data['email']);
403
        }
404
405
        // Save the user
406
        if (! $id = $this->user_model->insert($user_data))
407
        {
408
            $this->error = $this->user_model->error();
409
            return false;
410
        }
411
412
        $data = [
413
            'user_id' => $id,
414
            'email'   => $user_data['email'],
415
            'token'   => $token,
416
            'method'  => $method
417
        ];
418
419
        Events::trigger('didRegisterUser', [$data]);
420
421
        return true;
422
    }
423
424
    //--------------------------------------------------------------------
425
426
    /**
427
     * Used to verify the user values and activate a user so they can
428
     * visit the site.
429
     *
430
     * @param $data
431
     * @return bool
432
     */
433
    public function activateUser($data)
434
    {
435
        $post = [
436
            'email'         => $data['email'],
437
            'activate_hash' => hash('sha1', config_item('auth.salt') . $data['code'])
438
        ];
439
440
        $user = $this->user_model->where($post)
441
                                 ->first();
442
443
        if (! $user) {
444
            $this->error = $this->user_model->error() ? $this->user_model->error() : lang('auth.activate_no_user');
445
446
            return false;
447
        }
448
449 View Code Duplication
        if (! $this->user_model->update($user->id, ['active' => 1, 'activate_hash' => null]))
450
        {
451
            $this->error = $this->user_model->error();
452
            return false;
453
        }
454
455
        Events::trigger('didActivate', [(array)$user]);
456
457
        return true;
458
    }
459
460
    //--------------------------------------------------------------------
461
462
    /**
463
     * Used to allow manual activation of a user with a known ID.
464
     *
465
     * @param $id
466
     * @return bool
467
     */
468
    public function activateUserById($id)
469
    {
470 View Code Duplication
        if (! $this->user_model->update($id, ['active' => 1, 'activate_hash' => null]))
471
        {
472
            $this->error = $this->user_model->error();
473
            return false;
474
        }
475
476
        Events::trigger('didActivate', [$this->user_model->as_array()->find($id)]);
477
478
        return true;
479
    }
480
481
    //--------------------------------------------------------------------
482
483
    /**
484
     * Grabs the current user object. Returns NULL if nothing found.
485
     *
486
     * @return array|null
487
     */
488
    public function user()
489
    {
490
        return $this->user;
491
    }
492
493
    //--------------------------------------------------------------------
494
495
    /**
496
     * A convenience method to grab the current user's ID.
497
     *
498
     * @return int|null
499
     */
500
    public function id()
501
    {
502
        if (! is_array($this->user) || empty($this->user['id']))
503
        {
504
            return null;
505
        }
506
507
        return (int)$this->user['id'];
508
    }
509
510
    //--------------------------------------------------------------------
511
512
    /**
513
     * Checks to see if the user is currently being throttled.
514
     *
515
     *  - If they are NOT, will return FALSE.
516
     *  - If they ARE, will return the number of seconds until they can try again.
517
     *
518
     * @param $email
519
     * @return mixed
520
     */
521
    public function isThrottled($email)
522
    {
523
        // Not throttling? Get outta here!
524
        if (! config_item('auth.allow_throttling'))
525
        {
526
            return false;
527
        }
528
529
        // Emails should NOT be case sensitive.
530
        $email = strtolower($email);
531
532
        // Have any attempts been made?
533
        $attempts = $this->ci->login_model->countLoginAttempts($email);
534
535
        // Grab the amount of time to add if the system thinks we're
536
        // under a distributed brute force attack.
537
        // Affect users that have at least 1 failure login attempt
538
        $dbrute_time = ($attempts === 0) ? 0 : $this->ci->login_model->distributedBruteForceTime();
539
540
        // If this user was found to possibly be under a brute
541
        // force attack, their account would have been banned
542
        // for 15 minutes.
543
        if ($time = isset($_SESSION['bruteBan']) ? $_SESSION['bruteBan'] : false)
544
        {
545
            // If the current time is less than the
546
            // the ban expiration, plus any distributed time
547
            // then the user can't login just yet.
548
            if ($time + $dbrute_time > time())
549
            {
550
                // The user is banned still...
551
                $this->error = lang('auth.bruteBan_notice');
552
                return ($time + $dbrute_time) - time();
553
            }
554
555
            // Still here? The the ban time is over...
556
            unset($_SESSION['bruteBan']);
557
        }
558
559
        // Grab the time of last attempt and
560
        // determine if we're throttled by amount of time passed.
561
        $last_time = $this->ci->login_model->lastLoginAttemptTime($email);
562
563
        $allowed = config_item('auth.allowed_login_attempts');
564
565
        // We're not throttling if there are 0 attempts or
566
        // the number is less than or equal to the allowed free attempts
567
        if ($attempts === 0 || $attempts <= $allowed)
568
        {
569
            // Before we can say there's nothing up here,
570
            // we need to check dbrute time.
571
            $time_left = $last_time + $dbrute_time - time();
572
573
            if ($time_left > 0)
574
            {
575
                return $time_left;
576
            }
577
578
            return false;
579
        }
580
581
        // If the number of attempts is excessive (above 100) we need
582
        // to check the elapsed time of all of these attacks. If they are
583
        // less than 1 minute it's obvious this is a brute force attack,
584
        // so we'll set a session flag and block that user for 15 minutes.
585
        if ($attempts > 100 && $this->ci->login_model->isBruteForced($email))
586
        {
587
            $this->error = lang('auth.bruteBan_notice');
588
589
            $ban_time = 60 * 15;    // 15 minutes
590
            $_SESSION['bruteBan'] = time() + $ban_time;
591
            return $ban_time;
592
        }
593
594
        // Get our allowed attempts out of the picture.
595
        $attempts = $attempts - $allowed;
596
597
        $max_time = config_item('auth.max_throttle_time');
598
599
        $add_time = 5 * pow(2, $attempts - 1);
600
601
        if ($add_time > $max_time)
602
        {
603
            $add_time = $max_time;
604
        }
605
606
        $next_time = $last_time + $add_time + $dbrute_time;
607
608
        $current = time();
609
610
        // We are NOT throttled if we are already
611
        // past the allowed time.
612
        if ($current > $next_time)
613
        {
614
            return false;
615
        }
616
617
        return $next_time - $current;
618
    }
619
620
    //--------------------------------------------------------------------
621
622
    /**
623
     * Sends a password reset link email to the user associated with
624
     * the passed in $email.
625
     *
626
     * @param $email
627
     * @return mixed
628
     */
629
    public function remindUser($email)
630
    {
631
        // Emails should NOT be case sensitive.
632
        $email = strtolower($email);
633
634
        // Is it a valid user?
635
        $user = $this->user_model->find_by('email', $email);
636
637
        if (! $user)
638
        {
639
            $this->error = lang('auth.invalid_email');
640
            return false;
641
        }
642
643
        // Generate/store our codes
644
        $this->ci->load->helper('string');
645
        $token = random_string('alnum', 24);
646
        $hash = hash('sha1', config_item('auth.salt') .$token);
647
648
        $result = $this->user_model->update($user->id, ['reset_hash' => $hash]);
649
650
        if (! $result)
651
        {
652
            $this->error = $this->user_model->error();
653
            return false;
654
        }
655
656
        Events::trigger('didRemindUser', [(array)$user, $token]);
657
658
        return true;
659
    }
660
661
    //--------------------------------------------------------------------
662
663
    /**
664
     * Validates the credentials provided and, if valid, resets the password.
665
     *
666
     * The $credentials array MUST contain a 'code' key with the string to
667
     * hash and check against the reset_hash.
668
     *
669
     * @param $credentials
670
     * @param $password
671
     * @param $passConfirm
672
     * @return mixed
673
     */
674
    public function resetPassword($credentials, $password, $passConfirm)
675
    {
676
        if (empty($credentials['code']))
677
        {
678
            $this->error = lang('auth.need_reset_code');
679
            return false;
680
        }
681
682
        // Generate a hash to match against the table.
683
        $credentials['reset_hash'] = hash('sha1', config_item('auth.salt') .$credentials['code']);
684
        unset($credentials['code']);
685
686
        if (! empty($credentials['email']))
687
        {
688
            $credentials['email'] = strtolower($credentials['email']);
689
        }
690
691
        // Is there a matching user?
692
        $user = $this->user_model->find_by($credentials);
693
694
        if (! $user)
695
        {
696
            $this->error = lang('auth.reset_no_user');
697
            return false;
698
        }
699
700
        // Update their password and reset their reset_hash
701
        $data = [
702
            'password'     => $password,
703
            'pass_confirm' => $passConfirm,
704
            'reset_hash'   => null
705
        ];
706
707
        if (! $this->user_model->update($user->id, $data))
708
        {
709
            $this->error = $this->user_model->error();
710
            return false;
711
        }
712
713
        Events::trigger('didResetPassword', [(array)$user]);
714
715
        return true;
716
    }
717
718
    //--------------------------------------------------------------------
719
720
    /**
721
     * Provides a way for implementations to allow new statuses to be set
722
     * on the user. The details will vary based upon implementation, but
723
     * will often allow for banning or suspending users.
724
     *
725
     * @param $newStatus
726
     * @param null $message
727
     * @return mixed
728
     */
729
    public function changeStatus($newStatus, $message=null)
730
    {
731
        // todo actually record new users status!
732
    }
733
734
    //--------------------------------------------------------------------
735
736
    /**
737
     * Allows the consuming application to pass in a reference to the
738
     * model that should be used.
739
     *
740
     * The model MUST extend Myth\Models\CIDbModel.
741
     *
742
     * @param $model
743
     * @param bool $allow_any_parent
744
     * @return mixed
745
     */
746
    public function useModel($model, $allow_any_parent=false)
747
    {
748
        if (! $allow_any_parent && get_parent_class($model) != 'Myth\Models\CIDbModel')
749
        {
750
            throw new \RuntimeException('Models passed into LocalAuthenticate MUST extend Myth\Models\CIDbModel');
751
        }
752
753
        $this->user_model =& $model;
754
755
        return $this;
756
    }
757
758
    //--------------------------------------------------------------------
759
760
    public function error()
761
    {
762
        if (validation_errors())
763
        {
764
            return validation_errors();
765
        }
766
767
        return $this->error;
768
    }
769
770
    //--------------------------------------------------------------------
771
772
    //--------------------------------------------------------------------
773
    // Login Records
774
    //--------------------------------------------------------------------
775
776
    /**
777
     * Purges all login attempt records from the database.
778
     *
779
     * @param $email
780
     */
781 View Code Duplication
    public function purgeLoginAttempts($email)
782
    {
783
        // Emails should NOT be case sensitive.
784
        $email = strtolower($email);
785
786
        $this->ci->login_model->purgeLoginAttempts($email);
787
788
        // @todo record activity of login attempts purge.
789
        Events::trigger('didPurgeLoginAttempts', [$email]);
790
    }
791
792
    //--------------------------------------------------------------------
793
794
    /**
795
     * Purges all remember tokens for a single user. Effectively logs
796
     * a user out of all devices. Intended to allow users to log themselves
797
     * out of all devices as a security measure.
798
     *
799
     * @param $email
800
     */
801 View Code Duplication
    public function purgeRememberTokens($email)
802
    {
803
        // Emails should NOT be case sensitive.
804
        $email = strtolower($email);
805
806
        $this->ci->login_model->purgeRememberTokens($email);
807
808
        // todo record activity of remember me purges.
809
        Events::trigger('didPurgeRememberTokens', [$email]);
810
    }
811
812
    //--------------------------------------------------------------------
813
814
    //--------------------------------------------------------------------
815
    // Protected Methods
816
    //--------------------------------------------------------------------
817
818
    /**
819
     * Check if Allow Persistent Login Cookies is enable
820
     *
821
     * @param $user
822
     */
823
    protected function rememberUser($user)
824
    {
825
        if (! config_item('auth.allow_remembering'))
826
        {
827
            log_message('debug', 'Auth library set to refuse "Remember Me" functionality.');
828
            return false;
829
        }
830
831
        $this->refreshRememberCookie($user);
832
    }
833
834
    //--------------------------------------------------------------------
835
836
    /**
837
     * Invalidates the current rememberme cookie/database entry, creates
838
     * a new one, stores it and returns the new value.
839
     *
840
     * @param $user
841
     * @param null $token
842
     * @return mixed
843
     */
844
    protected function refreshRememberCookie($user, $token=null)
845
    {
846
        $this->ci->load->helper('cookie');
847
848
        // If a token is passed in, we know we're removing the
849
        // old one.
850
        if (! empty($token))
851
        {
852
            $this->invalidateRememberCookie($user['email'], $token);
853
        }
854
855
        $new_token = $this->ci->login_model->generateRememberToken($user);
856
857
        // Save the token to the database.
858
        $data = [
859
            'email'   => $user['email'],
860
            'hash'    => sha1(config_item('auth.salt') . $new_token),
861
            'created' => date('Y-m-d H:i:s')
862
        ];
863
864
        $this->ci->db->insert('auth_tokens', $data);
865
866
        // Create the cookie
867
        set_cookie(
868
            'remember',                             // Cookie Name
869
            $new_token,                             // Value
870
            config_item('auth.remember_length'),    // # Seconds until it expires
871
            config_item('cookie_domain'),
872
            config_item('cookie_path'),
873
            config_item('cookie_prefix'),
874
            false,                                  // Only send over HTTPS?
875
            true                                    // Hide from Javascript?
876
        );
877
878
        return $new_token;
879
    }
880
881
    //--------------------------------------------------------------------
882
883
    /**
884
     * Deletes any current remember me cookies and database entries.
885
     *
886
     * @param $email
887
     * @param $token
888
     * @return string The new token (not the hash).
889
     */
890
    protected function invalidateRememberCookie($email, $token)
891
    {
892
        // Emails should NOT be case sensitive.
893
        $email = strtolower($email);
894
895
        // Remove from the database
896
        $this->ci->login_model->deleteRememberToken($email, $token);
897
898
        // Remove the cookie
899
        delete_cookie(
900
            'remember',
901
            config_item('cookie_domain'),
902
            config_item('cookie_path'),
903
            config_item('cookie_prefix')
904
        );
905
    }
906
907
    //--------------------------------------------------------------------
908
909
    /**
910
     * Handles the nitty gritty of actually logging our user into the system.
911
     * Does NOT perform the authentication, just sets the system up so that
912
     * it knows we're here.
913
     *
914
     * @param $user
915
     */
916
    protected function loginUser($user)
917
    {
918
        // Save the user for later access
919
        $this->user = $user;
920
921
        // Regenerate the session ID to help protect
922
        // against session fixation
923
        $this->ci->session->sess_regenerate();
924
925
        // Let the session know that we're logged in.
926
        $this->ci->session->set_userdata('logged_in', $user['id']);
927
928
        // Clear our login attempts
929
        $this->ci->login_model->purgeLoginAttempts($user['email']);
930
931
        // Record a new Login
932
        $this->ci->login_model->recordLogin($user);
933
934
        // If logged in, ensure cache control
935
        // headers are in place
936
        $this->setHeaders();
937
938
        // We'll give a 20% chance to need to do a purge since we
939
        // don't need to purge THAT often, it's just a maintenance issue.
940
        // to keep the table from getting out of control.
941
        if (mt_rand(1, 100) < 20)
942
        {
943
            $this->ci->login_model->purgeOldRememberTokens();
944
        }
945
    }
946
947
    //--------------------------------------------------------------------
948
949
    /**
950
     * Sets the headers to ensure that pages are not cached when a user
951
     * is logged in, helping to protect against logging out and then
952
     * simply hitting the Back button on the browser and getting private
953
     * information because the page was loaded from cache.
954
     */
955
    protected function setHeaders()
956
    {
957
        $this->ci->output->set_header('Cache-Control: no-store, no-cache, must-revalidate');
958
        $this->ci->output->set_header('Cache-Control: post-check=0, pre-check=0');
959
        $this->ci->output->set_header('Pragma: no-cache');
960
    }
961
962
    //--------------------------------------------------------------------
963
964
965
}
966