This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
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
|
|||
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. ![]() |
|||
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);
}
}
![]() |
|||
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. ![]() |
|||
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. ![]() |
|||
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
|
|||
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 |
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.