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 ( e2250a...6bb0fd )
by Lonnie
08:13
created

LocalAuthentication::validate()   C

Complexity

Conditions 11
Paths 22

Size

Total Lines 82
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 82
rs 5.2653
cc 11
eloc 38
nc 22
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 throttling time is above zero, we can't allow
114
        // logins now.
115
        $time = (int)$this->isThrottled($user);
116
        if ($time > 0)
117
        {
118
            $this->error = sprintf(lang('auth.throttled'), $time);
119
            return false;
120
        }
121
122
        if (! $user)
123
        {
124
            if (empty($this->error))
125
            {
126
                // We need to set an error if there is no one
127
                $this->error = lang('auth.invalid_user');
128
            }
129
            $this->user = null;
130
            return $user;
131
        }       
132
133
        $this->loginUser($user);
134
135
        if ($remember)
136
        {
137
            $this->rememberUser($user);
138
        }
139
140
        Events::trigger('didLogin', [$user]);
141
142
        return true;
143
    }
144
145
    //--------------------------------------------------------------------
146
147
    /**
148
     * Validates user login information without logging them in.
149
     *
150
     * $credentials is an array of key/value pairs needed to log the user in.
151
     * This is often email/password, or username/password.
152
     *
153
     * @param $credentials
154
     * @param bool $return_user
155
     * @return mixed
156
     */
157
    public function validate($credentials, $return_user=false)
158
    {
159
        // Get ip address
160
        $ip_address = $this->ci->input->ip_address();
161
162
        // We do not want to force case-sensitivity on things
163
        // like username and email for usability sake.
164
        if (! empty($credentials['email']))
165
        {
166
            $credentials['email'] = strtolower($credentials['email']);
167
        }
168
169
        // Can't validate without a password.
170
        if (empty($credentials['password']) || count($credentials) < 2)
171
        {
172
            $this->ci->login_model->recordLoginAttempt($ip_address);
173
            return null;
174
        }
175
176
        $password = $credentials['password'];
177
        unset($credentials['password']);
178
179
        // We should only be allowed 1 single other credential to
180
        // test against.
181
        if (count($credentials) > 1)
182
        {
183
            $this->error = lang('auth.too_many_credentials');
184
            $this->ci->login_model->recordLoginAttempt($ip_address);
185
            return false;
186
        }
187
188
        // Ensure that the fields are allowed validation fields
189
        if (! in_array(key($credentials), config_item('auth.valid_fields')) )
190
        {
191
            $this->error = lang('auth.invalid_credentials');
192
            $this->ci->login_model->recordLoginAttempt($ip_address);
193
            return false;
194
        }
195
196
        // Can we find a user with those credentials?
197
        $user = $this->user_model->as_array()
198
                                 ->where($credentials)
199
                                 ->first();
200
201
        if (! $user)
202
        {
203
            $this->error = lang('auth.invalid_user');
204
            $this->ci->login_model->recordLoginAttempt($ip_address);
205
            return false;
206
        }
207
208
        // Now, try matching the passwords.
209
        $result =  password_verify($password, $user['password_hash']);
210
211
        if (! $result)
212
        {
213
            $this->error = lang('auth.invalid_password');
214
            $this->ci->login_model->recordLoginAttempt($ip_address, $user['id']);
215
            return false;
216
        }
217
218
        // Check to see if the password needs to be rehashed.
219
        // This would be due to the hash algorithm or hash
220
        // cost changing since the last time that a user
221
        // logged in.
222
        if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT, ['cost' => config_item('auth.hash_cost')] ))
223
        {
224
            $new_hash = Password::hashPassword($password);
225
            $this->user_model->skip_validation()
226
                             ->update($user['id'], ['password_hash' => $new_hash]);
227
            unset($new_hash);
228
        }
229
230
        // Is the user active?
231
        if (! $user['active'])
232
        {
233
            $this->error = lang('auth.inactive_account');
234
            return false;
235
        }
236
237
        return $return_user ? $user : true;
238
    }
239
240
    //--------------------------------------------------------------------
241
242
    /**
243
     * Logs a user out and removes all session information.
244
     *
245
     * @return mixed
246
     */
247
    public function logout()
248
    {
249
        $this->ci->load->helper('cookie');
250
251
        if (! Events::trigger('beforeLogout', [$this->user]))
252
        {
253
            return false;
254
        }
255
256
        // Destroy the session data - but ensure a session is still
257
        // available for flash messages, etc.
258
        if (isset($_SESSION))
259
        {
260
            foreach ( $_SESSION as $key => $value )
261
            {
262
                $_SESSION[ $key ] = NULL;
263
                unset( $_SESSION[ $key ] );
264
            }
265
        }
266
        // Also, regenerate the session ID for a touch of added safety.
267
        $this->ci->session->sess_regenerate(true);
268
269
        // Take care of any rememberme functionality.
270
        if (config_item('auth.allow_remembering'))
271
        {
272
            $token = get_cookie('remember');
273
274
            $this->invalidateRememberCookie($this->user['email'], $token);
275
        }
276
    }
277
278
    //--------------------------------------------------------------------
279
280
    /**
281
     * Checks whether a user is logged in or not.
282
     *
283
     * @return bool
284
     */
285
    public function isLoggedIn()
286
    {
287
        $id = $this->ci->session->userdata('logged_in');
288
289
        if (! $id)
290
        {
291
            return false;
292
        }
293
294
        // If the user var hasn't been filled in, we need to fill it in,
295
        // since this method will typically be used as the only method
296
        // to determine whether a user is logged in or not.
297
        if (! $this->user)
298
        {
299
            $this->user = $this->user_model->as_array()
300
                                           ->find_by('id', (int)$id);
301
302
            if (empty($this->user))
303
            {
304
                return false;
305
            }
306
        }
307
308
        // If logged in, ensure cache control
309
        // headers are in place
310
        $this->setHeaders();
311
312
        return true;
313
    }
314
315
    //--------------------------------------------------------------------
316
317
    /**
318
     * Attempts to log a user in based on the "remember me" cookie.
319
     *
320
     * @return bool
321
     */
322
    public function viaRemember()
323
    {
324
        if (! config_item('auth.allow_remembering'))
325
        {
326
            return false;
327
        }
328
329
        $this->ci->load->helper('cookie');
330
331
        if (! $token = get_cookie('remember'))
332
        {
333
            return false;
334
        }
335
336
        // Attempt to match the token against our auth_tokens table.
337
        $query = $this->ci->db->where('hash', $this->ci->login_model->hashRememberToken($token))
338
                              ->get('auth_tokens');
339
340
        if (! $query->num_rows())
341
        {
342
            return false;
343
        }
344
345
        // Grab the user
346
        $email = $query->row()->email;
347
348
        $user = $this->user_model->as_array()
349
                                 ->find_by('email', $email);
350
351
        $this->loginUser($user);
352
353
        // We only want our remember me tokens to be valid
354
        // for a single use.
355
        $this->refreshRememberCookie($user, $token);
356
357
        return true;
358
    }
359
360
    //--------------------------------------------------------------------
361
362
    /**
363
     * Registers a new user and handles activation method.
364
     *
365
     * @param $user_data
366
     * @return bool
367
     */
368
    public function registerUser($user_data)
369
    {
370
        // Anything special needed for Activation?
371
        $method = config_item('auth.activation_method');
372
373
        $user_data['active'] = $method == 'auto' ? 1 : 0;
374
375
        // If via email, we need to generate a hash
376
        $this->ci->load->helper('string');
377
        $token = random_string('alnum', 24);
378
        $user_data['activate_hash'] = hash('sha1', config_item('auth.salt') . $token);
379
380
        // Email should NOT be case sensitive.
381
        if (! empty($user_data['email']))
382
        {
383
            $user_data['email'] = strtolower($user_data['email']);
384
        }
385
386
        // Save the user
387
        if (! $id = $this->user_model->insert($user_data))
388
        {
389
            $this->error = $this->user_model->error();
390
            return false;
391
        }
392
393
        $data = [
394
            'user_id' => $id,
395
            'email'   => $user_data['email'],
396
            'token'   => $token,
397
            'method'  => $method
398
        ];
399
400
        Events::trigger('didRegisterUser', [$data]);
401
402
        return true;
403
    }
404
405
    //--------------------------------------------------------------------
406
407
    /**
408
     * Used to verify the user values and activate a user so they can
409
     * visit the site.
410
     *
411
     * @param $data
412
     * @return bool
413
     */
414
    public function activateUser($data)
415
    {
416
        $post = [
417
            'email'         => $data['email'],
418
            'activate_hash' => hash('sha1', config_item('auth.salt') . $data['code'])
419
        ];
420
421
        $user = $this->user_model->where($post)
422
                                 ->first();
423
424
        if (! $user) {
425
            $this->error = $this->user_model->error() ? $this->user_model->error() : lang('auth.activate_no_user');
426
427
            return false;
428
        }
429
430 View Code Duplication
        if (! $this->user_model->update($user->id, ['active' => 1, 'activate_hash' => null]))
431
        {
432
            $this->error = $this->user_model->error();
433
            return false;
434
        }
435
436
        Events::trigger('didActivate', [(array)$user]);
437
438
        return true;
439
    }
440
441
    //--------------------------------------------------------------------
442
443
    /**
444
     * Used to allow manual activation of a user with a known ID.
445
     *
446
     * @param $id
447
     * @return bool
448
     */
449
    public function activateUserById($id)
450
    {
451 View Code Duplication
        if (! $this->user_model->update($id, ['active' => 1, 'activate_hash' => null]))
452
        {
453
            $this->error = $this->user_model->error();
454
            return false;
455
        }
456
457
        Events::trigger('didActivate', [$this->user_model->as_array()->find($id)]);
458
459
        return true;
460
    }
461
462
    //--------------------------------------------------------------------
463
464
    /**
465
     * Grabs the current user object. Returns NULL if nothing found.
466
     *
467
     * @return array|null
468
     */
469
    public function user()
470
    {
471
        return $this->user;
472
    }
473
474
    //--------------------------------------------------------------------
475
476
    /**
477
     * A convenience method to grab the current user's ID.
478
     *
479
     * @return int|null
480
     */
481
    public function id()
482
    {
483
        if (! is_array($this->user) || empty($this->user['id']))
484
        {
485
            return null;
486
        }
487
488
        return (int)$this->user['id'];
489
    }
490
491
    //--------------------------------------------------------------------
492
493
    /**
494
     * Checks to see if the user is currently being throttled.
495
     *
496
     *  - If they are NOT, will return FALSE.
497
     *  - If they ARE, will return the number of seconds until they can try again.
498
     *
499
     * @param $email
500
     * @return mixed
501
     */
502
    public function isThrottled($user)
0 ignored issues
show
Coding Style introduced by
isThrottled uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

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