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 ( f306bc...a766f8 )
by Lonnie
16:59 queued 11:09
created
myth/Auth/LocalAuthentication.php 1 patch
Indentation   +937 added lines, -937 removed lines patch added patch discarded remove patch
@@ -1,34 +1,34 @@  discard block
 block discarded – undo
1 1
 <?php namespace Myth\Auth;
2 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
- */
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 32
 
33 33
 use Myth\Auth\AuthenticateInterface;
34 34
 use Myth\Events\Events;
@@ -52,914 +52,914 @@  discard block
 block discarded – undo
52 52
  */
53 53
 class LocalAuthentication implements AuthenticateInterface {
54 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
-        if (count($credentials) > 1)
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
-        if (! $user)
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
-        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
-        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 = pow(5, $attempts);
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
-    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
-    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
-    //--------------------------------------------------------------------
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
+		if (count($credentials) > 1)
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
+		if (! $user)
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
+		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
+		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 = pow(5, $attempts);
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
+	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
+	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 963
 
964 964
 
965 965
 }
Please login to merge, or discard this patch.