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 — develop (#780)
by
unknown
03:10
created

RegistrationModel::sendVerificationEmail()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 24
rs 8.9714
cc 2
eloc 13
nc 2
nop 3
1
<?php
2
3
/**
4
 * Class RegistrationModel
5
 *
6
 * Everything registration-related happens here.
7
 */
8
class RegistrationModel
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
     * Handles the entire registration process for DEFAULT users (not for people who register with
12
     * 3rd party services, like facebook) and creates a new user in the database if everything is fine
13
     *
14
     * @return boolean Gives back the success status of the registration
15
     */
16
    public static function registerNewUser()
17
    {
18
        // clean the input
19
        $user_name = strip_tags(Request::post('user_name'));
20
        $user_email = strip_tags(Request::post('user_email'));
21
        $user_email_repeat = strip_tags(Request::post('user_email_repeat'));
22
        $user_password_new = Request::post('user_password_new');
23
        $user_password_repeat = Request::post('user_password_repeat');
24
25
        // stop registration flow if registrationInputValidation() returns false (= anything breaks the input check rules)
26
        $validation_result = self::registrationInputValidation(Request::post('captcha'), $user_name, $user_password_new, $user_password_repeat, $user_email, $user_email_repeat);
27
        if (!$validation_result) {
28
            return false;
29
        }
30
31
        // crypt the password with the PHP 5.5's password_hash() function, results in a 60 character hash string.
32
        // @see php.net/manual/en/function.password-hash.php for more, especially for potential options
33
        $user_password_hash = password_hash($user_password_new, PASSWORD_DEFAULT);
34
35
        // make return a bool variable, so both errors can come up at once if needed
36
        $return = true;
37
38
        // check if username already exists
39
        if (UserModel::doesUsernameAlreadyExist($user_name)) {
40
            Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_ALREADY_TAKEN'));
41
            $return = false;
42
        }
43
44
        // check if email already exists
45
        if (UserModel::doesEmailAlreadyExist($user_email)) {
46
            Session::add('feedback_negative', Text::get('FEEDBACK_USER_EMAIL_ALREADY_TAKEN'));
47
            $return = false;
48
        }
49
50
        // if Username or Email were false, return false
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
51
        if (!$return) return false;
52
53
        // generate random hash for email verification (40 char string)
54
        $user_activation_hash = sha1(uniqid(mt_rand(), true));
55
56
        // write user data to database
57
        if (!self::writeNewUserToDatabase($user_name, $user_password_hash, $user_email, time(), $user_activation_hash)) {
58
            Session::add('feedback_negative', Text::get('FEEDBACK_ACCOUNT_CREATION_FAILED'));
59
            return false; // no reason not to return false here
60
        }
61
62
        // get user_id of the user that has been created, to keep things clean we DON'T use lastInsertId() here
63
        $user_id = UserModel::getUserIdByUsername($user_name);
64
65
        if (!$user_id) {
66
            Session::add('feedback_negative', Text::get('FEEDBACK_UNKNOWN_ERROR'));
67
            return false;
68
        }
69
70
        // send verification email
71
        if (self::sendVerificationEmail($user_id, $user_email, $user_activation_hash)) {
72
            Session::add('feedback_positive', Text::get('FEEDBACK_ACCOUNT_SUCCESSFULLY_CREATED'));
73
            return true;
74
        }
75
76
        // if verification email sending failed: instantly delete the user
77
        self::rollbackRegistrationByUserId($user_id);
78
        Session::add('feedback_negative', Text::get('FEEDBACK_VERIFICATION_MAIL_SENDING_FAILED'));
79
        return false;
80
    }
81
82
    /**
83
     * Validates the registration input
84
     *
85
     * @param $captcha
86
     * @param $user_name
87
     * @param $user_password_new
88
     * @param $user_password_repeat
89
     * @param $user_email
90
     * @param $user_email_repeat
91
     *
92
     * @return bool
93
     */
94
    public static function registrationInputValidation($captcha, $user_name, $user_password_new, $user_password_repeat, $user_email, $user_email_repeat)
95
    {
96
        $return = true;
97
98
        // perform all necessary checks
99
        if (!CaptchaModel::checkCaptcha($captcha)) {
100
            Session::add('feedback_negative', Text::get('FEEDBACK_CAPTCHA_WRONG'));
101
            $return = false;
102
        }
103
104
        // if username, email and password are all correctly validated, but make sure they all run on first sumbit
105
        if (self::validateUserName($user_name) AND self::validateUserEmail($user_email, $user_email_repeat) AND self::validateUserPassword($user_password_new, $user_password_repeat) AND $return) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
106
            return true;
107
        }
108
109
        // otherwise, return false
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
110
        return false;
111
    }
112
113
    /**
114
     * Validates the username
115
     *
116
     * @param $user_name
117
     * @return bool
118
     */
119
    public static function validateUserName($user_name)
120
    {
121
        if (empty($user_name)) {
122
            Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_FIELD_EMPTY'));
123
            return false;
124
        }
125
126
        // if username is too short (2), too long (64) or does not fit the pattern (aZ09)
127
        if (!preg_match('/^[a-zA-Z0-9]{2,64}$/', $user_name)) {
128
            Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_DOES_NOT_FIT_PATTERN'));
129
            return false;
130
        }
131
132
        return true;
133
    }
134
135
    /**
136
     * Validates the email
137
     *
138
     * @param $user_email
139
     * @param $user_email_repeat
140
     * @return bool
141
     */
142
    public static function validateUserEmail($user_email, $user_email_repeat)
143
    {
144
        if (empty($user_email)) {
145
            Session::add('feedback_negative', Text::get('FEEDBACK_EMAIL_FIELD_EMPTY'));
146
            return false;
147
        }
148
149
        if ($user_email !== $user_email_repeat) {
150
            Session::add('feedback_negative', Text::get('FEEDBACK_EMAIL_REPEAT_WRONG'));
151
            return false;
152
        }
153
154
        // validate the email with PHP's internal filter
155
        // side-fact: Max length seems to be 254 chars
156
        // @see http://stackoverflow.com/questions/386294/what-is-the-maximum-length-of-a-valid-email-address
157
        if (!filter_var($user_email, FILTER_VALIDATE_EMAIL)) {
158
            Session::add('feedback_negative', Text::get('FEEDBACK_EMAIL_DOES_NOT_FIT_PATTERN'));
159
            return false;
160
        }
161
162
        return true;
163
    }
164
165
    /**
166
     * Validates the password
167
     *
168
     * @param $user_password_new
169
     * @param $user_password_repeat
170
     * @return bool
171
     */
172
    public static function validateUserPassword($user_password_new, $user_password_repeat)
173
    {
174
        if (empty($user_password_new) OR empty($user_password_repeat)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
175
            Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_FIELD_EMPTY'));
176
            return false;
177
        }
178
179
        if ($user_password_new !== $user_password_repeat) {
180
            Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_REPEAT_WRONG'));
181
            return false;
182
        }
183
184
        if (strlen($user_password_new) < 6) {
185
            Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_TOO_SHORT'));
186
            return false;
187
        }
188
189
        return true;
190
    }
191
192
    /**
193
     * Writes the new user's data to the database
194
     *
195
     * @param $user_name
196
     * @param $user_password_hash
197
     * @param $user_email
198
     * @param $user_creation_timestamp
199
     * @param $user_activation_hash
200
     *
201
     * @return bool
202
     */
203
    public static function writeNewUserToDatabase($user_name, $user_password_hash, $user_email, $user_creation_timestamp, $user_activation_hash)
204
    {
205
        $database = DatabaseFactory::getFactory()->getConnection();
206
207
        // write new users data into database
208
        $sql = "INSERT INTO users (user_name, user_password_hash, user_email, user_creation_timestamp, user_activation_hash, user_provider_type)
209
                    VALUES (:user_name, :user_password_hash, :user_email, :user_creation_timestamp, :user_activation_hash, :user_provider_type)";
210
        $query = $database->prepare($sql);
211
        $query->execute(array(':user_name' => $user_name,
212
                              ':user_password_hash' => $user_password_hash,
213
                              ':user_email' => $user_email,
214
                              ':user_creation_timestamp' => $user_creation_timestamp,
215
                              ':user_activation_hash' => $user_activation_hash,
216
                              ':user_provider_type' => 'DEFAULT'));
217
        $count =  $query->rowCount();
218
        if ($count == 1) {
219
            return true;
220
        }
221
222
        return false;
223
    }
224
225
    /**
226
     * Deletes the user from users table. Currently used to rollback a registration when verification mail sending
227
     * was not successful.
228
     *
229
     * @param $user_id
230
     */
231
    public static function rollbackRegistrationByUserId($user_id)
232
    {
233
        $database = DatabaseFactory::getFactory()->getConnection();
234
235
        $query = $database->prepare("DELETE FROM users WHERE user_id = :user_id");
236
        $query->execute(array(':user_id' => $user_id));
237
    }
238
239
    /**
240
     * Sends the verification email (to confirm the account).
241
     * The construction of the mail $body looks weird at first, but it's really just a simple string.
242
     *
243
     * @param int $user_id user's id
244
     * @param string $user_email user's email
245
     * @param string $user_activation_hash user's mail verification hash string
246
     *
247
     * @return boolean gives back true if mail has been sent, gives back false if no mail could been sent
248
     */
249
    public static function sendVerificationEmail($user_id, $user_email, $user_activation_hash)
250
    {
251
        // Hash user's id and user's activation hash together.
252
        // We get nice looking hashed value without any special chars.
253
        // Sha256 used because of discussion here https://github.com/panique/huge/issues/776 (not sure is it good or not yet)
254
        // To be more secure, salt or secret key can be added to hash function.
255
        $hashed_user_data = hash('sha256' , $user_id . $user_activation_hash);
256
257
        $body = Config::get('EMAIL_VERIFICATION_CONTENT') . Config::get('URL') . Config::get('EMAIL_VERIFICATION_URL')
258
                . '/' . urlencode($hashed_user_data) . '/' . urlencode($user_activation_hash);
259
260
        $mail = new Mail;
261
        $mail_sent = $mail->sendMail($user_email, Config::get('EMAIL_VERIFICATION_FROM_EMAIL'),
262
            Config::get('EMAIL_VERIFICATION_FROM_NAME'), Config::get('EMAIL_VERIFICATION_SUBJECT'), $body
263
        );
264
265
        if ($mail_sent) {
266
            Session::add('feedback_positive', Text::get('FEEDBACK_VERIFICATION_MAIL_SENDING_SUCCESSFUL'));
267
            return true;
268
        } else {
269
            Session::add('feedback_negative', Text::get('FEEDBACK_VERIFICATION_MAIL_SENDING_ERROR') . $mail->getError() );
270
            return false;
271
        }
272
    }
273
274
    /**
275
     * checks the email/verification code combination and set the user's activation status to true in the database
276
     *
277
     * @param string $hashed_user_data Hashed user's id and user's activation verification code together.
278
     * @param string $user_activation_verification_code verification token
279
     *
280
     * @return bool success status
281
     */
282
    public static function verifyNewUser($hashed_user_data, $user_activation_verification_code)
283
    {
284
        $database = DatabaseFactory::getFactory()->getConnection();
285
286
        // Get user data (id and user_activation_hash) by activation code sent by email to user
287
        // Data from DB wiil be checked with parameters passed to method.
288
        $user_data = UserModel::getUserDataByUserActivationHash($user_activation_verification_code);
289
290
        // No user with that verification code -> return false
291
        if (!$user_data) {
292
            Session::add('feedback_negative', Text::get('FEEDBACK_ACCOUNT_ACTIVATION_FAILED'));
293
            return false;
294
        }
295
296
        // Hash user_id and user_activation_hash from DB the same way as sent in email (see line 255 in this file)
297
        $user_db_data_hash = hash('sha256', $user_data->user_id . $user_data->user_activation_hash);
298
299
        if ($user_db_data_hash === $hashed_user_data) {
300
301
            $sql = "UPDATE users
302
                       SET user_active = 1,
303
                           user_activation_hash = NULL
304
                     WHERE user_id = :user_id
305
                       AND user_activation_hash = :user_activation_hash
306
                     LIMIT 1
307
            ";
308
            $query = $database->prepare($sql);
309
            $query->execute(array(
310
                                    ':user_id' => $user_data->user_id,
311
                                    ':user_activation_hash' => $user_data->user_activation_hash
312
            ));
313
314
            if ($query->rowCount() == 1) {
315
                Session::add('feedback_positive', Text::get('FEEDBACK_ACCOUNT_ACTIVATION_SUCCESSFUL'));
316
                return true;
317
            }
318
        }
319
320
        Session::add('feedback_negative', Text::get('FEEDBACK_ACCOUNT_ACTIVATION_FAILED'));
321
        return false;
322
    }
323
}
324