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.

PasswordResetModel::saveChangedPassword()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.7333
c 0
b 0
f 0
cc 2
nc 2
nop 2
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
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_timestamp
132
                  FROM users
133
                 WHERE user_name = :user_name
134
                       AND user_password_reset_hash = :user_password_reset_hash
135
                       AND user_provider_type = :user_provider_type
136
                 LIMIT 1";
137
        $query = $database->prepare($sql);
138
        $query->execute(array(
139
            ':user_password_reset_hash' => $verification_code, ':user_name' => $user_name,
140
            ':user_provider_type' => 'DEFAULT'
141
        ));
142
143
        // if this user with exactly this verification hash code does NOT exist
144
        if ($query->rowCount() != 1) {
145
            Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_COMBINATION_DOES_NOT_EXIST'));
146
            return false;
147
        }
148
149
        // get result row (as an object)
150
        $result_user_row = $query->fetch();
151
152
        // 3600 seconds are 1 hour
153
        $timestamp_one_hour_ago = time() - 3600;
154
155
        // if password reset request was sent within the last hour (this timeout is for security reasons)
156
        if ($result_user_row->user_password_reset_timestamp > $timestamp_one_hour_ago) {
157
158
            // verification was successful
159
            Session::add('feedback_positive', Text::get('FEEDBACK_PASSWORD_RESET_LINK_VALID'));
160
            return true;
161
        } else {
162
            Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_LINK_EXPIRED'));
163
            return false;
164
        }
165
    }
166
167
    /**
168
     * Writes the new password to the database
169
     *
170
     * @param string $user_name username
171
     * @param string $user_password_hash
172
     * @param string $user_password_reset_hash
173
     *
174
     * @return bool
175
     */
176
    public static function saveNewUserPassword($user_name, $user_password_hash, $user_password_reset_hash)
177
    {
178
        $database = DatabaseFactory::getFactory()->getConnection();
179
180
        $sql = "UPDATE users SET user_password_hash = :user_password_hash, user_password_reset_hash = NULL,
181
                       user_password_reset_timestamp = NULL
182
                 WHERE user_name = :user_name AND user_password_reset_hash = :user_password_reset_hash
183
                       AND user_provider_type = :user_provider_type LIMIT 1";
184
        $query = $database->prepare($sql);
185
        $query->execute(array(
186
            ':user_password_hash' => $user_password_hash, ':user_name' => $user_name,
187
            ':user_password_reset_hash' => $user_password_reset_hash, ':user_provider_type' => 'DEFAULT'
188
        ));
189
190
        // if one result exists, return true, else false. Could be written even shorter btw.
191
        return ($query->rowCount() == 1 ? true : false);
192
    }
193
194
    /**
195
     * Set the new password (for DEFAULT user, FACEBOOK-users don't have a password)
196
     * Please note: At this point the user has already pre-verified via verifyPasswordReset() (within one hour),
197
     * so we don't need to check again for the 60min-limit here. In this method we authenticate
198
     * via username & password-reset-hash from (hidden) form fields.
199
     *
200
     * @param string $user_name
201
     * @param string $user_password_reset_hash
202
     * @param string $user_password_new
203
     * @param string $user_password_repeat
204
     *
205
     * @return bool success state of the password reset
206
     */
207
    public static function setNewPassword($user_name, $user_password_reset_hash, $user_password_new, $user_password_repeat)
208
    {
209
        // validate the password
210
        if (!self::validateResetPassword($user_name, $user_password_reset_hash, $user_password_new, $user_password_repeat)) {
211
            return false;
212
        }
213
214
        // crypt the password (with the PHP 5.5+'s password_hash() function, result is a 60 character hash string)
215
        $user_password_hash = password_hash($user_password_new, PASSWORD_DEFAULT);
216
217
        // write the password to database (as hashed and salted string), reset user_password_reset_hash
218
        if (self::saveNewUserPassword($user_name, $user_password_hash, $user_password_reset_hash)) {
219
            Session::add('feedback_positive', Text::get('FEEDBACK_PASSWORD_CHANGE_SUCCESSFUL'));
220
            return true;
221
        } else {
222
            Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_CHANGE_FAILED'));
223
            return false;
224
        }
225
    }
226
227
    /**
228
     * Validate the password submission
229
     *
230
     * @param $user_name
231
     * @param $user_password_reset_hash
232
     * @param $user_password_new
233
     * @param $user_password_repeat
234
     *
235
     * @return bool
236
     */
237
    public static function validateResetPassword($user_name, $user_password_reset_hash, $user_password_new, $user_password_repeat)
238
    {
239
        if (empty($user_name)) {
240
            Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_FIELD_EMPTY'));
241
            return false;
242
        } else if (empty($user_password_reset_hash)) {
243
            Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_TOKEN_MISSING'));
244
            return false;
245
        } else if (empty($user_password_new) || empty($user_password_repeat)) {
246
            Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_FIELD_EMPTY'));
247
            return false;
248
        } else if ($user_password_new !== $user_password_repeat) {
249
            Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_REPEAT_WRONG'));
250
            return false;
251
        } else if (strlen($user_password_new) < 6) {
252
            Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_TOO_SHORT'));
253
            return false;
254
        }
255
256
        return true;
257
    }
258
259
260
    /**
261
     * Writes the new password to the database
262
     *
263
     * @param string $user_name
264
     * @param string $user_password_hash
265
     *
266
     * @return bool
267
     */
268
    public static function saveChangedPassword($user_name, $user_password_hash)
269
    {
270
        $database = DatabaseFactory::getFactory()->getConnection();
271
272
        $sql = "UPDATE users SET user_password_hash = :user_password_hash
273
                 WHERE user_name = :user_name
274
                 AND user_provider_type = :user_provider_type LIMIT 1";
275
        $query = $database->prepare($sql);
276
        $query->execute(array(
277
            ':user_password_hash' => $user_password_hash, ':user_name' => $user_name,
278
            ':user_provider_type' => 'DEFAULT'
279
        ));
280
281
        // if one result exists, return true, else false. Could be written even shorter btw.
282
        return ($query->rowCount() == 1 ? true : false);
283
    }
284
285
286
    /**
287
     * Validates fields, hashes new password, saves new password
288
     *
289
     * @param string $user_name
290
     * @param string $user_password_current
291
     * @param string $user_password_new
292
     * @param string $user_password_repeat
293
     *
294
     * @return bool
295
     */
296
    public static function changePassword($user_name, $user_password_current, $user_password_new, $user_password_repeat)
297
    {
298
        // validate the passwords
299
        if (!self::validatePasswordChange($user_name, $user_password_current, $user_password_new, $user_password_repeat)) {
300
            return false;
301
        }
302
303
        // crypt the password (with the PHP 5.5+'s password_hash() function, result is a 60 character hash string)
304
        $user_password_hash = password_hash($user_password_new, PASSWORD_DEFAULT);
305
306
        // write the password to database (as hashed and salted string)
307
        if (self::saveChangedPassword($user_name, $user_password_hash)) {
308
            Session::add('feedback_positive', Text::get('FEEDBACK_PASSWORD_CHANGE_SUCCESSFUL'));
309
            return true;
310
        } else {
311
            Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_CHANGE_FAILED'));
312
            return false;
313
        }
314
    }
315
316
317
    /**
318
     * Validates current and new passwords
319
     *
320
     * @param string $user_name
321
     * @param string $user_password_current
322
     * @param string $user_password_new
323
     * @param string $user_password_repeat
324
     *
325
     * @return bool
326
     */
327
    public static function validatePasswordChange($user_name, $user_password_current, $user_password_new, $user_password_repeat)
328
    {
329
        $database = DatabaseFactory::getFactory()->getConnection();
330
331
        $sql = "SELECT user_password_hash, user_failed_logins FROM users WHERE user_name = :user_name LIMIT 1;";
332
        $query = $database->prepare($sql);
333
        $query->execute(array(
334
            ':user_name' => $user_name
335
        ));
336
337
        $user = $query->fetch();
338
339
        if ($query->rowCount() == 1) {
340
            $user_password_hash = $user->user_password_hash;
341
        } else {
342
            Session::add('feedback_negative', Text::get('FEEDBACK_USER_DOES_NOT_EXIST'));
343
            return false;
344
        }
345
346
        if (!password_verify($user_password_current, $user_password_hash)) {
347
            Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_CURRENT_INCORRECT'));
348
            return false;
349
        } else if (empty($user_password_new) || empty($user_password_repeat)) {
350
            Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_FIELD_EMPTY'));
351
            return false;
352
        } else if ($user_password_new !== $user_password_repeat) {
353
            Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_REPEAT_WRONG'));
354
            return false;
355
        } else if (strlen($user_password_new) < 6) {
356
            Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_TOO_SHORT'));
357
            return false;
358
        } else if ($user_password_current == $user_password_new){
359
            Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_NEW_SAME_AS_CURRENT'));
360
            return false;
361
        }
362
363
        return true;
364
    }
365
}
366