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
Pull Request — master (#778)
by
unknown
02:16
created

PasswordResetModel::expirePasswordReset()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 25
rs 8.8571
cc 2
eloc 12
nc 2
nop 1
1
<?php
2
3
/**
4
 * Class PasswordResetModel
5
 *
6
 * Handles all the stuff that is related to the password-reset process
7
 */
8
class PasswordResetModel
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
9
{
10
	/**
11
	 * Perform the necessary actions to send a password reset mail
12
	 *
13
	 * @param $user_name_or_email string Username or user's email
14
	 * @param $captcha string Captcha string
15
	 *
16
	 * @return bool success status
17
	 */
18
	public static function requestPasswordReset($user_name_or_email, $captcha)
19
	{
20
		if (!CaptchaModel::checkCaptcha($captcha)) {
21
			Session::add('feedback_negative', Text::get('FEEDBACK_CAPTCHA_WRONG'));
22
			return false;
23
		}
24
25
		if (empty($user_name_or_email)) {
26
			Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_EMAIL_FIELD_EMPTY'));
27
			return false;
28
		}
29
30
		// check if that username exists
31
		$result = UserModel::getUserDataByUserNameOrEmail($user_name_or_email);
32
		if (!$result) {
33
			Session::add('feedback_negative', Text::get('FEEDBACK_USER_DOES_NOT_EXIST'));
34
			return false;
35
		}
36
37
		// generate integer-timestamp (to see when exactly the user (or an attacker) requested the password reset mail)
38
		// generate random hash for email password reset verification (40 char string)
39
		$temporary_timestamp = time();
40
		$user_password_reset_hash = sha1(uniqid(mt_rand(), true));
41
42
		// set token (= a random hash string and a timestamp) into database ...
43
		$token_set = self::setPasswordResetDatabaseToken($result->user_name, $user_password_reset_hash, $temporary_timestamp);
44
		if (!$token_set) {
45
			return false;
46
		}
47
48
		// ... and send a mail to the user, containing a link with username and token hash string
49
		$mail_sent = self::sendPasswordResetMail($result->user_name, $user_password_reset_hash, $result->user_email);
50
		if ($mail_sent) {
51
			return true;
52
		}
53
54
		// default return
55
		return false;
56
	}
57
58
	/**
59
	 * Set password reset token in database (for DEFAULT user accounts)
60
	 *
61
	 * @param string $user_name username
62
	 * @param string $user_password_reset_hash password reset hash
63
	 * @param int $temporary_timestamp timestamp
64
	 *
65
	 * @return bool success status
66
	 */
67
	public static function setPasswordResetDatabaseToken($user_name, $user_password_reset_hash, $temporary_timestamp)
68
	{
69
		$database = DatabaseFactory::getFactory()->getConnection();
70
71
		$sql = "UPDATE users
72
                SET user_password_reset_hash = :user_password_reset_hash, user_password_reset_timestamp = :user_password_reset_timestamp
73
                WHERE user_name = :user_name AND user_provider_type = :provider_type LIMIT 1";
74
		$query = $database->prepare($sql);
75
		$query->execute(array(
76
			':user_password_reset_hash' => $user_password_reset_hash, ':user_name' => $user_name,
77
			':user_password_reset_timestamp' => $temporary_timestamp, ':provider_type' => 'DEFAULT'
78
		));
79
80
		// check if exactly one row was successfully changed
81
		if ($query->rowCount() == 1) {
82
			return true;
83
		}
84
85
		// fallback
86
		Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_TOKEN_FAIL'));
87
		return false;
88
	}
89
90
	/**
91
	 * Send the password reset mail
92
	 *
93
	 * @param string $user_name username
94
	 * @param string $user_password_reset_hash password reset hash
95
	 * @param string $user_email user email
96
	 *
97
	 * @return bool success status
98
	 */
99
	public static function sendPasswordResetMail($user_name, $user_password_reset_hash, $user_email)
100
	{
101
		// create email body
102
		$body = Config::get('EMAIL_PASSWORD_RESET_CONTENT') . ' ' . Config::get('URL') .
103
		        Config::get('EMAIL_PASSWORD_RESET_URL') . '/' . urlencode($user_name) . '/' . urlencode($user_password_reset_hash);
104
105
		// create instance of Mail class, try sending and check
106
		$mail = new Mail;
107
		$mail_sent = $mail->sendMail($user_email, Config::get('EMAIL_PASSWORD_RESET_FROM_EMAIL'), 
108
            Config::get('EMAIL_PASSWORD_RESET_FROM_NAME'), Config::get('EMAIL_PASSWORD_RESET_SUBJECT'), $body
109
		);
110
111
		if ($mail_sent) {
112
			Session::add('feedback_positive', Text::get('FEEDBACK_PASSWORD_RESET_MAIL_SENDING_SUCCESSFUL'));
113
			return true;
114
		}
115
116
		Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_MAIL_SENDING_ERROR') . $mail->getError() );
117
		return false;
118
	}
119
120
	/**
121
	 * Verifies the password reset request via the verification hash token (that's only valid for one hour)
122
	 * @param string $user_name Username
123
	 * @param string $verification_code Hash token
124
	 * @return bool Success status
125
	 */
126
	public static function verifyPasswordReset($user_name, $verification_code)
127
	{
128
		$database = DatabaseFactory::getFactory()->getConnection();
129
130
		// check if user-provided username + verification code combination exists
131
		$sql = "SELECT user_id, user_password_reset_hash, user_password_reset_timestamp
132
                  FROM users
133
                 WHERE user_name = :user_name
134
                       AND user_provider_type = :user_provider_type
135
                 LIMIT 1";
136
		$query = $database->prepare($sql);
137
		$query->execute(array(
138
			':user_name' => $user_name,
139
			':user_provider_type' => 'DEFAULT'
140
		));
141
		
142
		//if username does not exist
143
		if($query->rowCount() != 1) {
144
			Session::add('feedback_negative', Text::get('FEEDBACK_USER_DOES_NOT_EXIST'));
145
			return false;
146
		}
147
148
		// get result row (as an object)
149
		$result_user_row = $query->fetch();
150
		
151
		// if this user's verification hash code does NOT exist
152
		if ($result_user_row->user_password_reset_hash == NULL) {
153
			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_TOKEN_INVALID'));
154
			return false;
155
		}
156
		
157
		// if this user's verification hash code does not match
158
		// marks existing request as expired
159
		if ($result_user_row->user_password_reset_hash != $verification_code) {
160
			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_TOKEN_INVALID'));
161
			self::expirePasswordReset($user_name);
162
			return false;
163
		}
164
		
165
		// 3600 seconds are 1 hour
166
		$timestamp_one_hour_ago = time() - 3600;
167
168
		// if password reset request was sent within the last hour (this timeout is for security reasons)
169
		if ($result_user_row->user_password_reset_timestamp > $timestamp_one_hour_ago) {
170
			// verification was successful
171
			Session::add('feedback_positive', Text::get('FEEDBACK_PASSWORD_RESET_LINK_VALID'));
172
			return true;
173
		} else {
174
			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_LINK_EXPIRED'));
175
			return false;
176
		}
177
	}
178
	
179
	/**
180
	 * Marks existing password reset request as expired to prevent prevent guessing hash codes
181
	 * @param string $user_name Username
182
	 * @return bool Success status
183
	 */
184
	private static function expirePasswordReset($user_name) {
185
		// 3600 seconds are 1 hour
186
		$timestamp_one_hour_ago = time() - 3600;
187
		
188
		$database = DatabaseFactory::getFactory()->getConnection();
189
190
		$sql = "UPDATE users
191
                SET user_password_reset_timestamp = :user_password_reset_timestamp
192
                WHERE user_name = :user_name AND user_provider_type = :provider_type LIMIT 1";
193
		$query = $database->prepare($sql);
194
		$query->execute(array(
195
			':user_name' => $user_name,
196
			':user_password_reset_timestamp' => $timestamp_one_hour_ago,
197
			':provider_type' => 'DEFAULT'
198
		));
199
200
		// check if exactly one row was successfully changed
201
		if ($query->rowCount() == 1) {
202
			return true;
203
		}
204
205
		// user feedback not necessary here
206
		// but adding a note in the logs may be useful
207
		return false;
208
	}
209
210
	/**
211
	 * Writes the new password to the database
212
	 *
213
	 * @param string $user_name username
214
	 * @param string $user_password_hash
215
	 * @param string $user_password_reset_hash
216
	 *
217
	 * @return bool
218
	 */
219
	public static function saveNewUserPassword($user_name, $user_password_hash, $user_password_reset_hash)
220
	{
221
		$database = DatabaseFactory::getFactory()->getConnection();
222
223
		$sql = "UPDATE users SET user_password_hash = :user_password_hash, user_password_reset_hash = NULL,
224
                       user_password_reset_timestamp = NULL
225
                 WHERE user_name = :user_name AND user_password_reset_hash = :user_password_reset_hash
226
                       AND user_provider_type = :user_provider_type LIMIT 1";
227
		$query = $database->prepare($sql);
228
		$query->execute(array(
229
			':user_password_hash' => $user_password_hash, ':user_name' => $user_name,
230
			':user_password_reset_hash' => $user_password_reset_hash, ':user_provider_type' => 'DEFAULT'
231
		));
232
233
		// if one result exists, return true, else false. Could be written even shorter btw.
234
		return ($query->rowCount() == 1 ? true : false);
235
	}
236
237
	/**
238
	 * Set the new password (for DEFAULT user, FACEBOOK-users don't have a password)
239
	 * Please note: At this point the user has already pre-verified via verifyPasswordReset() (within one hour),
240
	 * so we don't need to check again for the 60min-limit here. In this method we authenticate
241
	 * via username & password-reset-hash from (hidden) form fields.
242
	 *
243
	 * @param string $user_name
244
	 * @param string $user_password_reset_hash
245
	 * @param string $user_password_new
246
	 * @param string $user_password_repeat
247
	 *
248
	 * @return bool success state of the password reset
249
	 */
250
	public static function setNewPassword($user_name, $user_password_reset_hash, $user_password_new, $user_password_repeat)
251
	{
252
		// validate the password
253
		if (!self::validateResetPassword($user_name, $user_password_reset_hash, $user_password_new, $user_password_repeat)) {
254
			return false;
255
		}
256
257
		// crypt the password (with the PHP 5.5+'s password_hash() function, result is a 60 character hash string)
258
		$user_password_hash = password_hash($user_password_new, PASSWORD_DEFAULT);
259
260
		// write the password to database (as hashed and salted string), reset user_password_reset_hash
261
		if (self::saveNewUserPassword($user_name, $user_password_hash, $user_password_reset_hash)) {
262
			Session::add('feedback_positive', Text::get('FEEDBACK_PASSWORD_CHANGE_SUCCESSFUL'));
263
			return true;
264
		} else {
265
			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_CHANGE_FAILED'));
266
			return false;
267
		}
268
	}
269
270
	/**
271
	 * Validate the password submission
272
	 *
273
	 * @param $user_name
274
	 * @param $user_password_reset_hash
275
	 * @param $user_password_new
276
	 * @param $user_password_repeat
277
	 *
278
	 * @return bool
279
	 */
280
	public static function validateResetPassword($user_name, $user_password_reset_hash, $user_password_new, $user_password_repeat)
281
	{
282
		if (empty($user_name)) {
283
			Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_FIELD_EMPTY'));
284
			return false;
285
		} else if (empty($user_password_reset_hash)) {
286
			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_TOKEN_MISSING'));
287
			return false;
288
		} else if (empty($user_password_new) || empty($user_password_repeat)) {
289
			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_FIELD_EMPTY'));
290
			return false;
291
		} else if ($user_password_new !== $user_password_repeat) {
292
			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_REPEAT_WRONG'));
293
			return false;
294
		} else if (strlen($user_password_new) < 6) {
295
			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_TOO_SHORT'));
296
			return false;
297
		}
298
299
		return true;
300
	}
301
302
303
	/**
304
	 * Writes the new password to the database
305
	 *
306
	 * @param string $user_name
307
	 * @param string $user_password_hash
308
	 *
309
	 * @return bool
310
	 */
311
	public static function saveChangedPassword($user_name, $user_password_hash)
312
	{
313
		$database = DatabaseFactory::getFactory()->getConnection();
314
315
		$sql = "UPDATE users SET user_password_hash = :user_password_hash
316
                 WHERE user_name = :user_name
317
                 AND user_provider_type = :user_provider_type LIMIT 1";
318
		$query = $database->prepare($sql);
319
		$query->execute(array(
320
			':user_password_hash' => $user_password_hash, ':user_name' => $user_name,
321
			':user_provider_type' => 'DEFAULT'
322
		));
323
324
		// if one result exists, return true, else false. Could be written even shorter btw.
325
		return ($query->rowCount() == 1 ? true : false);
326
	}
327
328
329
	/**
330
	 * Validates fields, hashes new password, saves new password
331
	 *
332
	 * @param string $user_name
333
	 * @param string $user_password_current
334
	 * @param string $user_password_new
335
	 * @param string $user_password_repeat
336
	 *
337
	 * @return bool
338
	 */
339
	public static function changePassword($user_name, $user_password_current, $user_password_new, $user_password_repeat)
340
	{
341
		// validate the passwords
342
		if (!self::validatePasswordChange($user_name, $user_password_current, $user_password_new, $user_password_repeat)) {
343
			return false;
344
		}
345
346
		// crypt the password (with the PHP 5.5+'s password_hash() function, result is a 60 character hash string)
347
		$user_password_hash = password_hash($user_password_new, PASSWORD_DEFAULT);
348
349
		// write the password to database (as hashed and salted string)
350
		if (self::saveChangedPassword($user_name, $user_password_hash)) {
351
			Session::add('feedback_positive', Text::get('FEEDBACK_PASSWORD_CHANGE_SUCCESSFUL'));
352
			return true;
353
		} else {
354
			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_CHANGE_FAILED'));
355
			return false;
356
		}
357
	}
358
359
360
	/**
361
	 * Validates current and new passwords
362
	 *
363
	 * @param string $user_name
364
	 * @param string $user_password_current
365
	 * @param string $user_password_new
366
	 * @param string $user_password_repeat
367
	 *
368
	 * @return bool
369
	 */
370
	public static function validatePasswordChange($user_name, $user_password_current, $user_password_new, $user_password_repeat)
371
	{
372
		$database = DatabaseFactory::getFactory()->getConnection();
373
374
		$sql = "SELECT user_password_hash, user_failed_logins FROM users WHERE user_name = :user_name LIMIT 1;";
375
		$query = $database->prepare($sql);
376
		$query->execute(array(
377
			':user_name' => $user_name
378
		));
379
380
		$user = $query->fetch();
381
382
        if ($query->rowCount() == 1) {
383
            $user_password_hash = $user->user_password_hash;
384
        } else {
385
            Session::add('feedback_negative', Text::get('FEEDBACK_USER_DOES_NOT_EXIST'));
386
            return false;
387
        }
388
389
		if (!password_verify($user_password_current, $user_password_hash)) {
390
			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_CURRENT_INCORRECT'));
391
			return false;
392
		} else if (empty($user_password_new) || empty($user_password_repeat)) {
393
			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_FIELD_EMPTY'));
394
			return false;
395
		} else if ($user_password_new !== $user_password_repeat) {
396
			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_REPEAT_WRONG'));
397
			return false;
398
		} else if (strlen($user_password_new) < 6) {
399
			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_TOO_SHORT'));
400
			return false;
401
		} else if ($user_password_current == $user_password_new){
402
			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_NEW_SAME_AS_CURRENT'));
403
			return false;
404
		}
405
406
		return true;
407
	}
408
}
409