Completed
Push — master ( 7ad65e...127008 )
by Charles
02:01
created

ResetPassword::validateAuthUser()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 9
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 9
loc 9
rs 9.6666
cc 3
eloc 5
nc 3
nop 2
1
<?php
2
3
namespace yrc\api\forms;
4
5
use Base32\Base32;
6
use Yii;
7
use yrc\api\models\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
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 View Code Duplication
    public function validateOTP($attributes, $params)
0 ignored issues
show
Unused Code introduced by
The parameter $attributes is not used and could be removed.

This check looks from 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.

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

Loading history...
Duplication introduced by
This method seems to be duplicated in 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.

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 View Code Duplication
    public function validateOldPassword($attributes, $params)
0 ignored issues
show
Unused Code introduced by
The parameter $attributes is not used and could be removed.

This check looks from 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.

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

Loading history...
Duplication introduced by
This method seems to be duplicated in 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.

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 View Code Duplication
    public function validateAuthUser($attributes, $params)
0 ignored issues
show
Unused Code introduced by
The parameter $attributes is not used and could be removed.

This check looks from 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.

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

Loading history...
Duplication introduced by
This method seems to be duplicated in 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.

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 View Code Duplication
    public function validateUser($attributes, $params)
0 ignored issues
show
Unused Code introduced by
The parameter $attributes is not used and could be removed.

This check looks from 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.

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

Loading history...
Duplication introduced by
This method seems to be duplicated in 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.

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 $attributes is not used and could be removed.

This check looks from 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.

This check looks from 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->yrc->userClass::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->yrc->userClass::findOne(['id' => $this->user_id]);
205
        } elseif (isset($this->email)) {
206
            $this->user = Yii::$app->yrc->userClass::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
                return Yii::$app->yrc->sendEmail('password_reset', Yii::t('app', 'A request has been made to change your password'), $this->getUser()->email, ['token' => $token]);
233
            } elseif ($this->getScenario() === self::SCENARIO_RESET || $this->getScenario() === self::SCENARIO_RESET_AUTHENTICATED) {
234
                $this->getUser()->password = $this->password;
235
236
                if ($this->getUser()->save()) {
237
                    return Yii::$app->yrc->sendEmail('password_change', Yii::t('app', 'Your password has been changed'), $this->email);
238
                }
239
            }
240
        }
241
242
        return false;
243
    }
244
}
245