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 ( 66d82f...f306bc )
by Lonnie
06:21
created

LocalAuthentication::validate()   D

Complexity

Conditions 15
Paths 30

Size

Total Lines 95
Code Lines 41

Duplication

Lines 20
Ratio 21.05 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 20
loc 95
rs 4.9121
cc 15
eloc 41
nc 30
nop 2

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
        // Grab the amount of time to add if the system thinks we're
533
        // under a distributed brute force attack.
534
        $dbrute_time = $this->ci->login_model->distributedBruteForceTime();
535
536
        // If this user was found to possibly be under a brute
537
        // force attack, their account would have been banned
538
        // for 15 minutes.
539
        if ($time = isset($_SESSION['bruteBan']) ? $_SESSION['bruteBan'] : false)
540
        {
541
            // If the current time is less than the
542
            // the ban expiration, plus any distributed time
543
            // then the user can't login just yet.
544
            if ($time + $dbrute_time > time())
545
            {
546
                // The user is banned still...
547
                $this->error = lang('auth.bruteBan_notice');
548
                return ($time + $dbrute_time) - time();
549
            }
550
551
            // Still here? The the ban time is over...
552
            unset($_SESSION['bruteBan']);
553
        }
554
555
        // Grab the time of last attempt and
556
        // determine if we're throttled by amount of time passed.
557
        $last_time = $this->ci->login_model->lastLoginAttemptTime($email);
558
559
        // Have any attempts been made?
560
        $attempts = $this->ci->login_model->countLoginAttempts($email);
561
562
        $allowed = config_item('auth.allowed_login_attempts');
563
564
        // We're not throttling if there are 0 attempts or
565
        // the number is less than or equal to the allowed free attempts
566
        if ($attempts === 0 || $attempts <= $allowed)
567
        {
568
            // Before we can say there's nothing up here,
569
            // we need to check dbrute time.
570
            $time_left = $last_time + $dbrute_time - time();
571
572
            if ($time_left > 0)
573
            {
574
                return $time_left;
575
            }
576
577
            return false;
578
        }
579
580
        // If the number of attempts is excessive (above 100) we need
581
        // to check the elapsed time of all of these attacks. If they are
582
        // less than 1 minute it's obvious this is a brute force attack,
583
        // so we'll set a session flag and block that user for 15 minutes.
584
        if ($attempts > 100 && $this->ci->login_model->isBruteForced($email))
585
        {
586
            $this->error = lang('auth.bruteBan_notice');
587
588
            $ban_time = 60 * 15;    // 15 minutes
589
            $_SESSION['bruteBan'] = time() + $ban_time;
590
            return $ban_time;
591
        }
592
593
        // Get our allowed attempts out of the picture.
594
        $attempts = $attempts - $allowed;
595
596
        $max_time = config_item('auth.max_throttle_time');
597
598
        $add_time = pow(5, $attempts);
599
600
        if ($add_time > $max_time)
601
        {
602
            $add_time = $max_time;
603
        }
604
605
        $next_time = $last_time + $add_time + $dbrute_time;
606
607
        $current = time();
608
609
        // We are NOT throttled if we are already
610
        // past the allowed time.
611
        if ($current > $next_time)
612
        {
613
            return false;
614
        }
615
616
        return $next_time - $current;
617
    }
618
619
    //--------------------------------------------------------------------
620
621
    /**
622
     * Sends a password reset link email to the user associated with
623
     * the passed in $email.
624
     *
625
     * @param $email
626
     * @return mixed
627
     */
628
    public function remindUser($email)
629
    {
630
        // Emails should NOT be case sensitive.
631
        $email = strtolower($email);
632
633
        // Is it a valid user?
634
        $user = $this->user_model->find_by('email', $email);
635
636
        if (! $user)
637
        {
638
            $this->error = lang('auth.invalid_email');
639
            return false;
640
        }
641
642
        // Generate/store our codes
643
        $this->ci->load->helper('string');
644
        $token = random_string('alnum', 24);
645
        $hash = hash('sha1', config_item('auth.salt') .$token);
646
647
        $result = $this->user_model->update($user->id, ['reset_hash' => $hash]);
648
649
        if (! $result)
650
        {
651
            $this->error = $this->user_model->error();
652
            return false;
653
        }
654
655
        Events::trigger('didRemindUser', [(array)$user, $token]);
656
657
        return true;
658
    }
659
660
    //--------------------------------------------------------------------
661
662
    /**
663
     * Validates the credentials provided and, if valid, resets the password.
664
     *
665
     * The $credentials array MUST contain a 'code' key with the string to
666
     * hash and check against the reset_hash.
667
     *
668
     * @param $credentials
669
     * @param $password
670
     * @param $passConfirm
671
     * @return mixed
672
     */
673
    public function resetPassword($credentials, $password, $passConfirm)
674
    {
675
        if (empty($credentials['code']))
676
        {
677
            $this->error = lang('auth.need_reset_code');
678
            return false;
679
        }
680
681
        // Generate a hash to match against the table.
682
        $credentials['reset_hash'] = hash('sha1', config_item('auth.salt') .$credentials['code']);
683
        unset($credentials['code']);
684
685
        if (! empty($credentials['email']))
686
        {
687
            $credentials['email'] = strtolower($credentials['email']);
688
        }
689
690
        // Is there a matching user?
691
        $user = $this->user_model->find_by($credentials);
692
693
        if (! $user)
694
        {
695
            $this->error = lang('auth.reset_no_user');
696
            return false;
697
        }
698
699
        // Update their password and reset their reset_hash
700
        $data = [
701
            'password'     => $password,
702
            'pass_confirm' => $passConfirm,
703
            'reset_hash'   => null
704
        ];
705
706
        if (! $this->user_model->update($user->id, $data))
707
        {
708
            $this->error = $this->user_model->error();
709
            return false;
710
        }
711
712
        Events::trigger('didResetPassword', [(array)$user]);
713
714
        return true;
715
    }
716
717
    //--------------------------------------------------------------------
718
719
    /**
720
     * Provides a way for implementations to allow new statuses to be set
721
     * on the user. The details will vary based upon implementation, but
722
     * will often allow for banning or suspending users.
723
     *
724
     * @param $newStatus
725
     * @param null $message
726
     * @return mixed
727
     */
728
    public function changeStatus($newStatus, $message=null)
729
    {
730
        // todo actually record new users status!
731
    }
732
733
    //--------------------------------------------------------------------
734
735
    /**
736
     * Allows the consuming application to pass in a reference to the
737
     * model that should be used.
738
     *
739
     * The model MUST extend Myth\Models\CIDbModel.
740
     *
741
     * @param $model
742
     * @param bool $allow_any_parent
743
     * @return mixed
744
     */
745
    public function useModel($model, $allow_any_parent=false)
746
    {
747
        if (! $allow_any_parent && get_parent_class($model) != 'Myth\Models\CIDbModel')
748
        {
749
            throw new \RuntimeException('Models passed into LocalAuthenticate MUST extend Myth\Models\CIDbModel');
750
        }
751
752
        $this->user_model =& $model;
753
754
        return $this;
755
    }
756
757
    //--------------------------------------------------------------------
758
759
    public function error()
760
    {
761
        if (validation_errors())
762
        {
763
            return validation_errors();
764
        }
765
766
        return $this->error;
767
    }
768
769
    //--------------------------------------------------------------------
770
771
    //--------------------------------------------------------------------
772
    // Login Records
773
    //--------------------------------------------------------------------
774
775
    /**
776
     * Purges all login attempt records from the database.
777
     *
778
     * @param $email
779
     */
780 View Code Duplication
    public function purgeLoginAttempts($email)
781
    {
782
        // Emails should NOT be case sensitive.
783
        $email = strtolower($email);
784
785
        $this->ci->login_model->purgeLoginAttempts($email);
786
787
        // @todo record activity of login attempts purge.
788
        Events::trigger('didPurgeLoginAttempts', [$email]);
789
    }
790
791
    //--------------------------------------------------------------------
792
793
    /**
794
     * Purges all remember tokens for a single user. Effectively logs
795
     * a user out of all devices. Intended to allow users to log themselves
796
     * out of all devices as a security measure.
797
     *
798
     * @param $email
799
     */
800 View Code Duplication
    public function purgeRememberTokens($email)
801
    {
802
        // Emails should NOT be case sensitive.
803
        $email = strtolower($email);
804
805
        $this->ci->login_model->purgeRememberTokens($email);
806
807
        // todo record activity of remember me purges.
808
        Events::trigger('didPurgeRememberTokens', [$email]);
809
    }
810
811
    //--------------------------------------------------------------------
812
813
    //--------------------------------------------------------------------
814
    // Protected Methods
815
    //--------------------------------------------------------------------
816
817
    /**
818
     * Check if Allow Persistent Login Cookies is enable
819
     *
820
     * @param $user
821
     */
822
    protected function rememberUser($user)
823
    {
824
        if (! config_item('auth.allow_remembering'))
825
        {
826
            log_message('debug', 'Auth library set to refuse "Remember Me" functionality.');
827
            return false;
828
        }
829
830
        $this->refreshRememberCookie($user);
831
    }
832
833
    //--------------------------------------------------------------------
834
835
    /**
836
     * Invalidates the current rememberme cookie/database entry, creates
837
     * a new one, stores it and returns the new value.
838
     *
839
     * @param $user
840
     * @param null $token
841
     * @return mixed
842
     */
843
    protected function refreshRememberCookie($user, $token=null)
844
    {
845
        $this->ci->load->helper('cookie');
846
847
        // If a token is passed in, we know we're removing the
848
        // old one.
849
        if (! empty($token))
850
        {
851
            $this->invalidateRememberCookie($user['email'], $token);
852
        }
853
854
        $new_token = $this->ci->login_model->generateRememberToken($user);
855
856
        // Save the token to the database.
857
        $data = [
858
            'email'   => $user['email'],
859
            'hash'    => sha1(config_item('auth.salt') . $new_token),
860
            'created' => date('Y-m-d H:i:s')
861
        ];
862
863
        $this->ci->db->insert('auth_tokens', $data);
864
865
        // Create the cookie
866
        set_cookie(
867
            'remember',                             // Cookie Name
868
            $new_token,                             // Value
869
            config_item('auth.remember_length'),    // # Seconds until it expires
870
            config_item('cookie_domain'),
871
            config_item('cookie_path'),
872
            config_item('cookie_prefix'),
873
            false,                                  // Only send over HTTPS?
874
            true                                    // Hide from Javascript?
875
        );
876
877
        return $new_token;
878
    }
879
880
    //--------------------------------------------------------------------
881
882
    /**
883
     * Deletes any current remember me cookies and database entries.
884
     *
885
     * @param $email
886
     * @param $token
887
     * @return string The new token (not the hash).
888
     */
889
    protected function invalidateRememberCookie($email, $token)
890
    {
891
        // Emails should NOT be case sensitive.
892
        $email = strtolower($email);
893
894
        // Remove from the database
895
        $this->ci->login_model->deleteRememberToken($email, $token);
896
897
        // Remove the cookie
898
        delete_cookie(
899
            'remember',
900
            config_item('cookie_domain'),
901
            config_item('cookie_path'),
902
            config_item('cookie_prefix')
903
        );
904
    }
905
906
    //--------------------------------------------------------------------
907
908
    /**
909
     * Handles the nitty gritty of actually logging our user into the system.
910
     * Does NOT perform the authentication, just sets the system up so that
911
     * it knows we're here.
912
     *
913
     * @param $user
914
     */
915
    protected function loginUser($user)
916
    {
917
        // Save the user for later access
918
        $this->user = $user;
919
920
        // Regenerate the session ID to help protect
921
        // against session fixation
922
        $this->ci->session->sess_regenerate();
923
924
        // Let the session know that we're logged in.
925
        $this->ci->session->set_userdata('logged_in', $user['id']);
926
927
        // Clear our login attempts
928
        $this->ci->login_model->purgeLoginAttempts($user['email']);
929
930
        // Record a new Login
931
        $this->ci->login_model->recordLogin($user);
932
933
        // If logged in, ensure cache control
934
        // headers are in place
935
        $this->setHeaders();
936
937
        // We'll give a 20% chance to need to do a purge since we
938
        // don't need to purge THAT often, it's just a maintenance issue.
939
        // to keep the table from getting out of control.
940
        if (mt_rand(1, 100) < 20)
941
        {
942
            $this->ci->login_model->purgeOldRememberTokens();
943
        }
944
    }
945
946
    //--------------------------------------------------------------------
947
948
    /**
949
     * Sets the headers to ensure that pages are not cached when a user
950
     * is logged in, helping to protect against logging out and then
951
     * simply hitting the Back button on the browser and getting private
952
     * information because the page was loaded from cache.
953
     */
954
    protected function setHeaders()
955
    {
956
        $this->ci->output->set_header('Cache-Control: no-store, no-cache, must-revalidate');
957
        $this->ci->output->set_header('Cache-Control: post-check=0, pre-check=0');
958
        $this->ci->output->set_header('Pragma: no-cache');
959
    }
960
961
    //--------------------------------------------------------------------
962
963
964
}
965