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.

Issues (423)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

myth/Auth/LocalAuthentication.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php namespace Myth\Auth;
2
/**
3
 * Sprint
4
 *
5
 * A set of power tools to enhance the CodeIgniter framework and provide consistent workflow.
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in
15
 * all copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
 * THE SOFTWARE.
24
 *
25
 * @package     Sprint
26
 * @author      Lonnie Ezell
27
 * @copyright   Copyright 2014-2015, New Myth Media, LLC (http://newmythmedia.com)
28
 * @license     http://opensource.org/licenses/MIT  (MIT)
29
 * @link        http://sprintphp.com
30
 * @since       Version 1.0
31
 */
32
33
use Myth\Auth\AuthenticateInterface;
34
use Myth\Events\Events;
35
36
/**
37
 * Class LocalAuthentication
38
 *
39
 * Provides most of the Authentication that web applications would need,
40
 * at least as far as local authentication goes. It does NOT provide
41
 * social authentication through third-party applications.
42
 *
43
 * The system attempts to incorporate as many of the ideas and best practices
44
 * set forth in the following documents:
45
 *
46
 *  - http://stackoverflow.com/questions/549/the-definitive-guide-to-form-based-website-authentication
47
 *  - https://www.owasp.org/index.php/Guide_to_Authentication
48
 *
49
 * todo: Set the error string for all error states here.
50
 *
51
 * @package Myth\Auth
52
 */
53
class LocalAuthentication implements AuthenticateInterface {
54
55
    protected $ci;
56
57
    protected $user = null;
58
59
    public $user_model = null;
60
61
    public $error = null;
62
63
    //--------------------------------------------------------------------
64
65
    public function __construct( $ci=null )
66
    {
67
        if ($ci)
68
        {
69
            $this->ci= $ci;
70
        }
71
        else
72
        {
73
            $this->ci =& get_instance();
74
        }
75
76
        // Get our compatibility password file loaded up.
77
        if (! function_exists('password_hash'))
78
        {
79
            require_once dirname(__FILE__) .'password.php';
80
        }
81
82
        if (empty($this->ci->session))
83
        {
84
            $this->ci->load->library('session');
85
        }
86
87
        $this->ci->config->load('auth');
88
        $this->ci->load->model('auth/login_model');
89
        $this->ci->load->language('auth/auth');
90
    }
91
92
    //--------------------------------------------------------------------
93
94
    /**
95
     * Attempt to log a user into the system.
96
     *
97
     * $credentials is an array of key/value pairs needed to log the user in.
98
     * This is often email/password, or username/password.
99
     *
100
     * @param array $credentials
101
     * @param bool  $remember
102
     * @return bool|mixed
103
     */
104
    public function login($credentials, $remember=false)
105
    {
106
        $user = $this->validate($credentials, true);
107
108
        if (! $user)
109
        {
110
            $this->user = null;
111
            return $user;
112
        }       
113
114
        $this->loginUser($user);
115
116
        if ($remember)
117
        {
118
            $this->rememberUser($user);
119
        }
120
121
        Events::trigger('didLogin', [$user]);
122
123
        return true;
124
    }
125
126
    //--------------------------------------------------------------------
127
128
    /**
129
     * Validates user login information without logging them in.
130
     *
131
     * $credentials is an array of key/value pairs needed to log the user in.
132
     * This is often email/password, or username/password.
133
     *
134
     * @param $credentials
135
     * @param bool $return_user
136
     * @return mixed
137
     */
138
    public function validate($credentials, $return_user=false)
139
    {
140
        // Can't validate without a password.
141
        if (empty($credentials['password']) || count($credentials) < 2)
142
        {
143
            return null;
144
        }
145
146
        $password = $credentials['password'];
147
        unset($credentials['password']);
148
149
        // We should only be allowed 1 single other credential to
150
        // test against.
151
        if (count($credentials) > 1)
152
        {
153
            $this->error = lang('auth.too_many_credentials');
154
            return false;
155
        }
156
157
        // Ensure that the fields are allowed validation fields
158
        if (! in_array(key($credentials), config_item('auth.valid_fields')) )
159
        {
160
            $this->error = lang('auth.invalid_credentials');
161
            return false;
162
        }
163
164
        // We do not want to force case-sensitivity on things
165
        // like username and email for usability sake.
166
        if (! empty($credentials['email']))
167
        {
168
            $credentials['email'] = strtolower($credentials['email']);
169
        }
170
171
        // Can we find a user with those credentials?
172
        $user = $this->user_model->as_array()
173
                                 ->where($credentials)
174
                                 ->first();
175
176
        // If the user is throttled due to too many invalid logins
177
        // or the system is under attack, kick them back.
178
179
        // If throttling time is above zero, we can't allow
180
        // logins now.
181
        $time = (int)$this->isThrottled($user);
182 View Code Duplication
        if ($time > 0)
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
183
        {
184
            $this->error = sprintf(lang('auth.throttled'), $time);
185
            return false;
186
        }
187
188
        // Get ip address
189
        $ip_address = $this->ci->input->ip_address();
190
191 View Code Duplication
        if (! $user)
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
192
        {
193
            $this->error = lang('auth.invalid_user');
194
            $this->ci->login_model->recordLoginAttempt($ip_address);
195
            return false;
196
        }
197
198
        // Now, try matching the passwords.
199
        $result =  password_verify($password, $user['password_hash']);
200
201
        if (! $result)
202
        {
203
            $this->error = lang('auth.invalid_password');
204
            $this->ci->login_model->recordLoginAttempt($ip_address, $user['id']);
205
            return false;
206
        }
207
208
        // Check to see if the password needs to be rehashed.
209
        // This would be due to the hash algorithm or hash
210
        // cost changing since the last time that a user
211
        // logged in.
212
        if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT, ['cost' => config_item('auth.hash_cost')] ))
213
        {
214
            $new_hash = Password::hashPassword($password);
215
            $this->user_model->skip_validation()
216
                             ->update($user['id'], ['password_hash' => $new_hash]);
217
            unset($new_hash);
218
        }
219
220
        // Is the user active?
221
        if (! $user['active'])
222
        {
223
            $this->error = lang('auth.inactive_account');
224
            return false;
225
        }
226
227
        return $return_user ? $user : true;
228
    }
229
230
    //--------------------------------------------------------------------
231
232
    /**
233
     * Logs a user out and removes all session information.
234
     *
235
     * @return mixed
236
     */
237
    public function logout()
238
    {
239
        $this->ci->load->helper('cookie');
240
241
        if (! Events::trigger('beforeLogout', [$this->user]))
242
        {
243
            return false;
244
        }
245
246
        // Destroy the session data - but ensure a session is still
247
        // available for flash messages, etc.
248
        if (isset($_SESSION))
249
        {
250
            foreach ( $_SESSION as $key => $value )
251
            {
252
                $_SESSION[ $key ] = NULL;
253
                unset( $_SESSION[ $key ] );
254
            }
255
        }
256
        // Also, regenerate the session ID for a touch of added safety.
257
        $this->ci->session->sess_regenerate(true);
258
259
        // Take care of any rememberme functionality.
260
        if (config_item('auth.allow_remembering'))
261
        {
262
            $token = get_cookie('remember');
263
264
            $this->invalidateRememberCookie($this->user['email'], $token);
265
        }
266
    }
267
268
    //--------------------------------------------------------------------
269
270
    /**
271
     * Checks whether a user is logged in or not.
272
     *
273
     * @return bool
274
     */
275
    public function isLoggedIn()
276
    {
277
        $id = $this->ci->session->userdata('logged_in');
278
279
        if (! $id)
280
        {
281
            return false;
282
        }
283
284
        // If the user var hasn't been filled in, we need to fill it in,
285
        // since this method will typically be used as the only method
286
        // to determine whether a user is logged in or not.
287
        if (! $this->user)
288
        {
289
            $this->user = $this->user_model->as_array()
290
                                           ->find_by('id', (int)$id);
291
292
            if (empty($this->user))
293
            {
294
                return false;
295
            }
296
        }
297
298
        // If logged in, ensure cache control
299
        // headers are in place
300
        $this->setHeaders();
301
302
        return true;
303
    }
304
305
    //--------------------------------------------------------------------
306
307
    /**
308
     * Attempts to log a user in based on the "remember me" cookie.
309
     *
310
     * @return bool
311
     */
312
    public function viaRemember()
313
    {
314
        if (! config_item('auth.allow_remembering'))
315
        {
316
            return false;
317
        }
318
319
        $this->ci->load->helper('cookie');
320
321
        if (! $token = get_cookie('remember'))
322
        {
323
            return false;
324
        }
325
326
        // Attempt to match the token against our auth_tokens table.
327
        $query = $this->ci->db->where('hash', $this->ci->login_model->hashRememberToken($token))
328
                              ->get('auth_tokens');
329
330
        if (! $query->num_rows())
331
        {
332
            return false;
333
        }
334
335
        // Grab the user
336
        $email = $query->row()->email;
337
338
        $user = $this->user_model->as_array()
339
                                 ->find_by('email', $email);
340
341
        $this->loginUser($user);
342
343
        // We only want our remember me tokens to be valid
344
        // for a single use.
345
        $this->refreshRememberCookie($user, $token);
346
347
        return true;
348
    }
349
350
    //--------------------------------------------------------------------
351
352
    /**
353
     * Registers a new user and handles activation method.
354
     *
355
     * @param $user_data
356
     * @return bool
357
     */
358
    public function registerUser($user_data)
359
    {
360
        // Anything special needed for Activation?
361
        $method = config_item('auth.activation_method');
362
363
        $user_data['active'] = $method == 'auto' ? 1 : 0;
364
365
        // If via email, we need to generate a hash
366
        $this->ci->load->helper('string');
367
        $token = random_string('alnum', 24);
368
        $user_data['activate_hash'] = hash('sha1', config_item('auth.salt') . $token);
369
370
        // Email should NOT be case sensitive.
371
        if (! empty($user_data['email']))
372
        {
373
            $user_data['email'] = strtolower($user_data['email']);
374
        }
375
376
        // Save the user
377
        if (! $id = $this->user_model->insert($user_data))
378
        {
379
            $this->error = $this->user_model->error();
380
            return false;
381
        }
382
383
        $data = [
384
            'user_id' => $id,
385
            'email'   => $user_data['email'],
386
            'token'   => $token,
387
            'method'  => $method
388
        ];
389
390
        Events::trigger('didRegisterUser', [$data]);
391
392
        return true;
393
    }
394
395
    //--------------------------------------------------------------------
396
397
    /**
398
     * Used to verify the user values and activate a user so they can
399
     * visit the site.
400
     *
401
     * @param $data
402
     * @return bool
403
     */
404
    public function activateUser($data)
405
    {
406
        $post = [
407
            'email'         => $data['email'],
408
            'activate_hash' => hash('sha1', config_item('auth.salt') . $data['code'])
409
        ];
410
411
        $user = $this->user_model->where($post)
412
                                 ->first();
413
414
        if (! $user) {
415
            $this->error = $this->user_model->error() ? $this->user_model->error() : lang('auth.activate_no_user');
416
417
            return false;
418
        }
419
420 View Code Duplication
        if (! $this->user_model->update($user->id, ['active' => 1, 'activate_hash' => null]))
421
        {
422
            $this->error = $this->user_model->error();
423
            return false;
424
        }
425
426
        Events::trigger('didActivate', [(array)$user]);
427
428
        return true;
429
    }
430
431
    //--------------------------------------------------------------------
432
433
    /**
434
     * Used to allow manual activation of a user with a known ID.
435
     *
436
     * @param $id
437
     * @return bool
438
     */
439
    public function activateUserById($id)
440
    {
441 View Code Duplication
        if (! $this->user_model->update($id, ['active' => 1, 'activate_hash' => null]))
442
        {
443
            $this->error = $this->user_model->error();
444
            return false;
445
        }
446
447
        Events::trigger('didActivate', [$this->user_model->as_array()->find($id)]);
448
449
        return true;
450
    }
451
452
    //--------------------------------------------------------------------
453
454
    /**
455
     * Grabs the current user object. Returns NULL if nothing found.
456
     *
457
     * @return array|null
458
     */
459
    public function user()
460
    {
461
        return $this->user;
462
    }
463
464
    //--------------------------------------------------------------------
465
466
    /**
467
     * A convenience method to grab the current user's ID.
468
     *
469
     * @return int|null
470
     */
471
    public function id()
472
    {
473
        if (! is_array($this->user) || empty($this->user['id']))
474
        {
475
            return null;
476
        }
477
478
        return (int)$this->user['id'];
479
    }
480
481
    //--------------------------------------------------------------------
482
483
    /**
484
     * Checks to see if the user is currently being throttled.
485
     *
486
     *  - If they are NOT, will return FALSE.
487
     *  - If they ARE, will return the number of seconds until they can try again.
488
     *
489
     * @param $user
490
     * @return mixed
491
     */
492
    public function isThrottled($user)
0 ignored issues
show
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...
493
    {
494
        // Not throttling? Get outta here!
495
        if (! config_item('auth.allow_throttling'))
496
        {
497
            return false;
498
        }
499
500
        // Get user_id
501
        $user_id = $user ? $user['id'] : null;
502
        
503
        // Get ip address
504
        $ip_address = $this->ci->input->ip_address();
505
506
        // Have any attempts been made?
507
        $attempts = $this->ci->login_model->countLoginAttempts($ip_address, $user_id);
508
509
        // Grab the amount of time to add if the system thinks we're
510
        // under a distributed brute force attack.
511
        // Affect users that have at least 1 failure login attempt
512
        $dbrute_time = ($attempts === 0) ? 0 : $this->ci->login_model->distributedBruteForceTime();
513
514
        // If this user was found to possibly be under a brute
515
        // force attack, their account would have been banned
516
        // for 15 minutes.
517
        if ($time = isset($_SESSION['bruteBan']) ? $_SESSION['bruteBan'] : false)
518
        {
519
            // If the current time is less than the
520
            // the ban expiration, plus any distributed time
521
            // then the user can't login just yet.
522
            if ($time + $dbrute_time > time())
523
            {
524
                // The user is banned still...
525
                $this->error = lang('auth.bruteBan_notice');
526
                return ($time + $dbrute_time) - time();
527
            }
528
529
            // Still here? The the ban time is over...
530
            unset($_SESSION['bruteBan']);
531
        }
532
533
        // Grab the time of last attempt and
534
        // determine if we're throttled by amount of time passed.
535
        $last_time = $this->ci->login_model->lastLoginAttemptTime($ip_address, $user_id);
536
537
        $allowed = config_item('auth.allowed_login_attempts');
538
539
        // We're not throttling if there are 0 attempts or
540
        // the number is less than or equal to the allowed free attempts
541
        if ($attempts === 0 || $attempts < $allowed)
542
        {
543
            // Before we can say there's nothing up here,
544
            // we need to check dbrute time.
545
            $time_left = $last_time + $dbrute_time - time();
546
547
            if ($time_left > 0)
548
            {
549
                return $time_left;
550
            }
551
552
            return false;
553
        }
554
555
        // If the number of attempts is excessive (above 100) we need
556
        // to check the elapsed time of all of these attacks. If they are
557
        // less than 1 minute it's obvious this is a brute force attack,
558
        // so we'll set a session flag and block that user for 15 minutes.
559
        if ($attempts > 100 && $this->ci->login_model->isBruteForced($ip_address, $user_id))
560
        {
561
            $this->error = lang('auth.bruteBan_notice');
562
563
            $ban_time = 60 * 15;    // 15 minutes
564
            $_SESSION['bruteBan'] = time() + $ban_time;
565
            return $ban_time;
566
        }
567
568
        // Get our allowed attempts out of the picture.
569
        $attempts = $attempts - $allowed;
570
571
        $max_time = config_item('auth.max_throttle_time');
572
573
        $add_time = 5 * pow(2, $attempts);
574
575
        if ($add_time > $max_time)
576
        {
577
            $add_time = $max_time;
578
        }
579
580
        $next_time = $last_time + $add_time + $dbrute_time;
581
582
        $current = time();
583
584
        // We are NOT throttled if we are already
585
        // past the allowed time.
586
        if ($current > $next_time)
587
        {
588
            return false;
589
        }
590
591
        return $next_time - $current;
592
    }
593
594
    //--------------------------------------------------------------------
595
596
    /**
597
     * Sends a password reset link email to the user associated with
598
     * the passed in $email.
599
     *
600
     * @param $email
601
     * @return mixed
602
     */
603
    public function remindUser($email)
604
    {
605
        // Emails should NOT be case sensitive.
606
        $email = strtolower($email);
607
608
        // Is it a valid user?
609
        $user = $this->user_model->find_by('email', $email);
610
611
        if (! $user)
612
        {
613
            $this->error = lang('auth.invalid_email');
614
            return false;
615
        }
616
617
        // Generate/store our codes
618
        $this->ci->load->helper('string');
619
        $token = random_string('alnum', 24);
620
        $hash = hash('sha1', config_item('auth.salt') .$token);
621
622
        $result = $this->user_model->update($user->id, ['reset_hash' => $hash]);
623
624
        if (! $result)
625
        {
626
            $this->error = $this->user_model->error();
627
            return false;
628
        }
629
630
        Events::trigger('didRemindUser', [(array)$user, $token]);
631
632
        return true;
633
    }
634
635
    //--------------------------------------------------------------------
636
637
    /**
638
     * Validates the credentials provided and, if valid, resets the password.
639
     *
640
     * The $credentials array MUST contain a 'code' key with the string to
641
     * hash and check against the reset_hash.
642
     *
643
     * @param $credentials
644
     * @param $password
645
     * @param $passConfirm
646
     * @return mixed
647
     */
648
    public function resetPassword($credentials, $password, $passConfirm)
649
    {
650
        if (empty($credentials['code']))
651
        {
652
            $this->error = lang('auth.need_reset_code');
653
            return false;
654
        }
655
656
        // Generate a hash to match against the table.
657
        $reset_hash = hash('sha1', config_item('auth.salt') .$credentials['code']);
658
        unset($credentials['code']);
659
660
        if (! empty($credentials['email']))
661
        {
662
            $credentials['email'] = strtolower($credentials['email']);
663
        }
664
665
        // Is there a matching user?
666
        $user = $this->user_model->as_array()
667
                                 ->where($credentials)
668
                                 ->first();
669
670
        // If throttling time is above zero, we can't allow
671
        // logins now.
672
        $time = (int)$this->isThrottled($user);
673 View Code Duplication
        if ($time > 0)
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
674
        {
675
            $this->error = sprintf(lang('auth.throttled'), $time);
676
            return false;
677
        }
678
679
        // Get ip address
680
        $ip_address = $this->ci->input->ip_address();
681
682 View Code Duplication
        if (! $user)
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
683
        {
684
            $this->error = lang('auth.reset_no_user');
685
            $this->ci->login_model->recordLoginAttempt($ip_address);
686
            return false;
687
        }
688
689
        // Is generated reset_hash string matches one from the table?
690
        if ($reset_hash !== $user['reset_hash'])
691
        {
692
            $this->error = lang('auth.reset_no_user');
693
            $this->ci->login_model->recordLoginAttempt($ip_address, $user['id']);
694
            return false;
695
        }
696
697
        // Update their password and reset their reset_hash
698
        $data = [
699
            'password'     => $password,
700
            'pass_confirm' => $passConfirm,
701
            'reset_hash'   => null
702
        ];
703
704
        if (! $this->user_model->update($user['id'], $data))
705
        {
706
            $this->error = $this->user_model->error();
707
            return false;
708
        }
709
710
        // Clear our login attempts
711
        $this->ci->login_model->purgeLoginAttempts($ip_address, $user['id']);
712
713
        Events::trigger('didResetPassword', [$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 null $ip_address
780
     * @param null $user_id
781
     */
782
    public function purgeLoginAttempts($ip_address = null, $user_id = null)
783
    {
784
        $this->ci->login_model->purgeLoginAttempts($ip_address, $user_id);
785
786
        // @todo record activity of login attempts purge.
787
        Events::trigger('didPurgeLoginAttempts', [$email]);
0 ignored issues
show
The variable $email does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
788
    }
789
790
    //--------------------------------------------------------------------
791
792
    /**
793
     * Purges all remember tokens for a single user. Effectively logs
794
     * a user out of all devices. Intended to allow users to log themselves
795
     * out of all devices as a security measure.
796
     *
797
     * @param $email
798
     */
799
    public function purgeRememberTokens($email)
800
    {
801
        // Emails should NOT be case sensitive.
802
        $email = strtolower($email);
803
804
        $this->ci->login_model->purgeRememberTokens($email);
805
806
        // todo record activity of remember me purges.
807
        Events::trigger('didPurgeRememberTokens', [$email]);
808
    }
809
810
    //--------------------------------------------------------------------
811
812
    //--------------------------------------------------------------------
813
    // Protected Methods
814
    //--------------------------------------------------------------------
815
816
    /**
817
     * Check if Allow Persistent Login Cookies is enable
818
     *
819
     * @param $user
820
     */
821
    protected function rememberUser($user)
822
    {
823
        if (! config_item('auth.allow_remembering'))
824
        {
825
            log_message('debug', 'Auth library set to refuse "Remember Me" functionality.');
826
            return false;
827
        }
828
829
        $this->refreshRememberCookie($user);
830
    }
831
832
    //--------------------------------------------------------------------
833
834
    /**
835
     * Invalidates the current rememberme cookie/database entry, creates
836
     * a new one, stores it and returns the new value.
837
     *
838
     * @param $user
839
     * @param null $token
840
     * @return mixed
841
     */
842
    protected function refreshRememberCookie($user, $token=null)
843
    {
844
        $this->ci->load->helper('cookie');
845
846
        // If a token is passed in, we know we're removing the
847
        // old one.
848
        if (! empty($token))
849
        {
850
            $this->invalidateRememberCookie($user['email'], $token);
851
        }
852
853
        $new_token = $this->ci->login_model->generateRememberToken($user);
854
855
        // Save the token to the database.
856
        $data = [
857
            'email'   => $user['email'],
858
            'hash'    => sha1(config_item('auth.salt') . $new_token),
859
            'created' => date('Y-m-d H:i:s')
860
        ];
861
862
        $this->ci->db->insert('auth_tokens', $data);
863
864
        // Create the cookie
865
        set_cookie(
866
            'remember',                             // Cookie Name
867
            $new_token,                             // Value
868
            config_item('auth.remember_length'),    // # Seconds until it expires
869
            config_item('cookie_domain'),
870
            config_item('cookie_path'),
871
            config_item('cookie_prefix'),
872
            false,                                  // Only send over HTTPS?
873
            true                                    // Hide from Javascript?
874
        );
875
876
        return $new_token;
877
    }
878
879
    //--------------------------------------------------------------------
880
881
    /**
882
     * Deletes any current remember me cookies and database entries.
883
     *
884
     * @param $email
885
     * @param $token
886
     * @return string The new token (not the hash).
887
     */
888
    protected function invalidateRememberCookie($email, $token)
889
    {
890
        // Emails should NOT be case sensitive.
891
        $email = strtolower($email);
892
893
        // Remove from the database
894
        $this->ci->login_model->deleteRememberToken($email, $token);
895
896
        // Remove the cookie
897
        delete_cookie(
898
            'remember',
899
            config_item('cookie_domain'),
900
            config_item('cookie_path'),
901
            config_item('cookie_prefix')
902
        );
903
    }
904
905
    //--------------------------------------------------------------------
906
907
    /**
908
     * Handles the nitty gritty of actually logging our user into the system.
909
     * Does NOT perform the authentication, just sets the system up so that
910
     * it knows we're here.
911
     *
912
     * @param $user
913
     */
914
    protected function loginUser($user)
915
    {
916
        // Save the user for later access
917
        $this->user = $user;
918
919
        // Get ip address
920
        $ip_address = $this->ci->input->ip_address();
921
922
        // Regenerate the session ID to help protect
923
        // against session fixation
924
        $this->ci->session->sess_regenerate();
925
926
        // Let the session know that we're logged in.
927
        $this->ci->session->set_userdata('logged_in', $user['id']);
928
929
        // Clear our login attempts
930
        $this->ci->login_model->purgeLoginAttempts($ip_address, $user['id']);
931
932
        // Record a new Login
933
        $this->ci->login_model->recordLogin($user);
934
935
        // If logged in, ensure cache control
936
        // headers are in place
937
        $this->setHeaders();
938
939
        // We'll give a 20% chance to need to do a purge since we
940
        // don't need to purge THAT often, it's just a maintenance issue.
941
        // to keep the table from getting out of control.
942
        if (mt_rand(1, 100) < 20)
943
        {
944
            $this->ci->login_model->purgeOldRememberTokens();
945
        }
946
    }
947
948
    //--------------------------------------------------------------------
949
950
    /**
951
     * Sets the headers to ensure that pages are not cached when a user
952
     * is logged in, helping to protect against logging out and then
953
     * simply hitting the Back button on the browser and getting private
954
     * information because the page was loaded from cache.
955
     */
956
    protected function setHeaders()
957
    {
958
        $this->ci->output->set_header('Cache-Control: no-store, no-cache, must-revalidate');
959
        $this->ci->output->set_header('Cache-Control: post-check=0, pre-check=0');
960
        $this->ci->output->set_header('Pragma: no-cache');
961
    }
962
963
    //--------------------------------------------------------------------
964
965
966
}
967