ResetPassword::validateResetToken()   A
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 14
nc 4
nop 2
dl 0
loc 22
rs 9.4888
c 0
b 0
f 0
1
<?php
2
3
namespace yrc\forms;
4
5
use Base32\Base32;
6
use Yii;
7
use yrc\models\redis\Code;
8
9
abstract class ResetPassword extends \yii\base\Model
10
{
11
    const SCENARIO_INIT = 'init';
12
    const SCENARIO_RESET = 'reset';
13
    const SCENARIO_RESET_AUTHENTICATED = 'reset_authenticated';
14
15
    // The expiration time of the token
16
    const EXPIRY_TIME = '+4 hours';
17
18
    /**
19
     * The email
20
     * @var string $email
21
     */
22
    public $email;
23
24
    /**
25
     * The reset token
26
     * @var string $reset_token
27
     */
28
    public $reset_token;
29
30
    /**
31
     * The user ID for credentialless password resets
32
     * @var int $user_id
33
     */
34
    public $user_id;
35
36
    /**
37
     * The old password
38
     * @var string $old_password
39
     */
40
    public $old_password;
41
42
    /**
43
     * The new password
44
     * @var string $password
45
     */
46
    public $password;
47
48
    /**
49
     * The new password (again)
50
     * @var string $password_verify
51
     */
52
    public $password_verify;
53
54
    /**
55
     * The OTP code (optional)
56
     * @var string $otp
57
     */
58
    public $otp;
59
60
    /**
61
     * The user associated to the email
62
     * @var User $user
0 ignored issues
show
Bug introduced by
The type yrc\forms\User was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
63
     */
64
    protected $user = null;
65
66
    /**
67
     * Validation rules
68
     * @return array
69
     */
70
    public function rules()
71
    {
72
        return [
73
            [['email'], 'required', 'on' => self::SCENARIO_INIT],
74
            [['email'], 'email', 'on' =>  self::SCENARIO_INIT],
75
            [['email'], 'validateUser', 'on' =>  self::SCENARIO_INIT],
76
77
            [['reset_token', 'password', 'password_verify'], 'required', 'on' => self::SCENARIO_RESET],
78
            [['reset_token'], 'validateResetToken', 'on' => self::SCENARIO_RESET],
79
            [['password', 'password_verify'], 'string', 'min' => 8, 'on' => self::SCENARIO_RESET],
80
            [['password_verify'], 'compare', 'compareAttribute' => 'password', 'on' => self::SCENARIO_RESET],
81
            [['password', 'password_verify'], 'required', 'on' => self::SCENARIO_RESET],
82
            [['otp'], 'validateOTP', 'on' => self::SCENARIO_RESET],
83
84
            [['user_id'], 'validateAuthUser', 'on' => self::SCENARIO_RESET_AUTHENTICATED],
85
            [['old_password'], 'validateOldPassword', 'on' => self::SCENARIO_RESET_AUTHENTICATED],
86
            [['old_password', 'password', 'password_verify'], 'required', 'on' => self::SCENARIO_RESET_AUTHENTICATED],
87
            [['password', 'password_verify'], 'string', 'min' => 8, 'on' => self::SCENARIO_RESET_AUTHENTICATED],
88
            [['password_verify'], 'compare', 'compareAttribute' => 'password', 'on' => self::SCENARIO_RESET_AUTHENTICATED],
89
            [['password', 'password_verify'], 'required', 'on' => self::SCENARIO_RESET_AUTHENTICATED],
90
            [['otp'], 'validateOTP', 'on' => self::SCENARIO_RESET_AUTHENTICATED],
91
        ];
92
    }
93
    
94
    /**
95
     * Validates the users OTP code, if they provided
96
     * @inheritdoc
97
     */
98
    public function validateOTP($attributes, $params)
0 ignored issues
show
Unused Code introduced by
The parameter $attributes is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

98
    public function validateOTP(/** @scrutinizer ignore-unused */ $attributes, $params)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $params is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

98
    public function validateOTP($attributes, /** @scrutinizer ignore-unused */ $params)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
99
    {
100
        if (!$this->hasErrors()) {
101
            if ($this->getUser()->isOTPEnabled()) {
102
                if ($this->otp === null || !$this->getUser()->verifyOTP((string)$this->otp)) {
103
                    $this->addError('otp', Yii::t('yrc', 'This account is protected with two factor authentication, and a valid OTP code is required to change the password.'));
104
                    return;
105
                }
106
            }
107
        }
108
    }
109
110
    /**
111
     * Validates the users old password
112
     * @inheritdoc
113
     */
114
    public function validateOldPassword($attributes, $params)
0 ignored issues
show
Unused Code introduced by
The parameter $params is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

114
    public function validateOldPassword($attributes, /** @scrutinizer ignore-unused */ $params)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $attributes is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

114
    public function validateOldPassword(/** @scrutinizer ignore-unused */ $attributes, $params)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
115
    {
116
        if (!$this->hasErrors()) {
117
            if (!$this->getUser()->validatePassword($this->old_password)) {
118
                $this->addError('old_password', Yii::t('yrc', 'Your current password is not valid.'));
119
                return;
120
            }
121
            
122
            $this->validateOTP('otp', $this->otp);
123
        }
124
    }
125
126
    /**
127
     * Validates the authenticated users state
128
     * @inheritdoc
129
     */
130
    public function validateAuthUser($attributes, $params)
0 ignored issues
show
Unused Code introduced by
The parameter $params is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

130
    public function validateAuthUser($attributes, /** @scrutinizer ignore-unused */ $params)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $attributes is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

130
    public function validateAuthUser(/** @scrutinizer ignore-unused */ $attributes, $params)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
131
    {
132
        if (!$this->hasErrors()) {
133
            if ($this->getUser() === null) {
134
                $this->addError('email', Yii::t('yrc', 'The provided email address is not valid.'));
135
                return;
136
            }
137
        }
138
    }
139
140
    /**
141
     * Validates the users email
142
     * @inheritdoc
143
     */
144
    public function validateUser($attributes, $params)
0 ignored issues
show
Unused Code introduced by
The parameter $params is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

144
    public function validateUser($attributes, /** @scrutinizer ignore-unused */ $params)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $attributes is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

144
    public function validateUser(/** @scrutinizer ignore-unused */ $attributes, $params)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
145
    {
146
        if (!$this->hasErrors()) {
147
            if ($this->getUser() === null) {
148
                $this->addError('email', Yii::t('yrc', 'The provided email address is not valid'));
149
                return;
150
            }
151
        }
152
    }
153
154
    /**
155
     * Reset token validator
156
     * @inheritdoc
157
     */
158
    public function validateResetToken($attributes, $params)
0 ignored issues
show
Unused Code introduced by
The parameter $params is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

158
    public function validateResetToken($attributes, /** @scrutinizer ignore-unused */ $params)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $attributes is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

158
    public function validateResetToken(/** @scrutinizer ignore-unused */ $attributes, $params)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
159
    {
160
        if (!$this->hasErrors()) {
161
            $code = Code::find()->where([
162
                'hash' => hash('sha256', $this->reset_token . '_reset_token')
163
            ])->one();
164
            
165
            if ($code === null || $code->isExpired()) {
166
                $this->addError('reset_token', Yii::t('yrc', 'The password reset token provided is not valid.'));
167
                return;
168
            }
169
170
            $this->setUser(Yii::$app->user->identityClass::find()->where([
171
                'id' => $code->user_id
172
            ])->one());
173
174
            if ($this->getUser() === null) {
175
                $this->addError('reset_token', Yii::t('yrc', 'The password reset token provided is not valid.'));
176
                return;
177
            }
178
179
            $this->validateOTP('otp', $this->otp);
180
        }
181
    }
182
183
    /**
184
     * Sets the user object
185
     * @param User $user
186
     */
187
    public function setUser($user)
188
    {
189
        $this->user = $user;
190
        $this->email = $user->email;
191
    }
192
193
    /**
194
     * Helper method to get the current user
195
     * @return User
196
     */
197
    public function getUser()
198
    {
199
        if ($this->user !== null) {
200
            return $this->user;
201
        }
202
203
        if (isset($this->user_id)) {
204
            $this->user = Yii::$app->user->identityClass::findOne(['id' => $this->user_id]);
205
        } elseif (isset($this->email)) {
206
            $this->user = Yii::$app->user->identityClass::findOne(['email' => $this->email]);
207
        }
208
209
        return $this->user;
210
    }
211
212
    /**
213
     * Changes the password for the user
214
     * @return boolean
215
     */
216
    public function reset()
217
    {
218
        if ($this->validate()) {
219
            if ($this->getScenario() === self::SCENARIO_INIT) {
220
                // Create an reset token for the user, and store it in the cache
221
                $token = Base32::encode(\random_bytes(64));
222
                
223
                $code = new Code;
224
                $code->hash = hash('sha256', $token . '_reset_token');
225
                $code->user_id = $this->getUser()->id;
226
                $code->expires_at = strtotime(self::EXPIRY_TIME);
227
228
                if (!$code->save()) {
229
                    return false;
230
                }
231
232
                $job = Yii::$app->rpq->getQueue()->push(
0 ignored issues
show
Bug introduced by
The method getQueue() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

232
                $job = Yii::$app->rpq->/** @scrutinizer ignore-call */ getQueue()->push(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
233
                    '\yrc\jobs\notifications\email\ResetPasswordNotification',
234
                    [
235
                        'email' => $this->getUser()->email,
236
                        'token' => $token,
237
                        'user_id' => $this->getUser()->id
238
                    ],
239
                    true
240
                );
241
    
242
                Yii::info([
243
                    'message' => '[Email] Reset Password Notification Scheduled',
244
                    'user_id' => $this->getUser()->id,
245
                    'data' => [
246
                        'email' => $this->getUser()->email
247
                    ],
248
                    'job_id' => $job->getId()
249
                ]);
250
251
                return true;
252
            } elseif ($this->getScenario() === self::SCENARIO_RESET || $this->getScenario() === self::SCENARIO_RESET_AUTHENTICATED) {
253
                $this->getUser()->password = $this->password;
254
255
                if ($this->getUser()->save()) {
256
                    $job = Yii::$app->rpq->getQueue()->push(
257
                        '\yrc\jobs\notifications\email\PasswordChangedNotification',
258
                        [
259
                            'email' => $this->getUser()->email,
260
                            'user_id' => $this->getUser()->id
261
                        ],
262
                        true
263
                    );
264
        
265
                    Yii::info([
266
                        'message' => '[Email] Password Changed Notification Scheduled',
267
                        'user_id' => $this->getUser()->id,
268
                        'data' => [
269
                            'email' => $this->getUser()->email
270
                        ],
271
                        'job_id' => $job->getId()
272
                    ]);
273
                    
274
                    return true;
275
                }
276
            }
277
        }
278
279
        return false;
280
    }
281
}
282