Completed
Push — master ( 92869a...884021 )
by Charles
02:51
created

ResetPassword::validatePassword()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 8
rs 9.4285
cc 3
eloc 4
nc 3
nop 2
1
<?php
2
3
namespace yrc\api\forms;
4
5
use Base32\Base32;
6
use Yii;
7
8
abstract class ResetPassword extends \yii\base\model
9
{
10
    const SCENARIO_INIT = 'init';
11
    const SCENARIO_RESET = 'reset';
12
13
    const EXPIRY_TIME = '+4 hours';
14
15
    /**
16
     * The email
17
     * @var string $email
18
     */
19
    public $email;
20
21
    /**
22
     * The reset token
23
     * @var string $reset_token
24
     */
25
    public $reset_token;
26
27
    /**
28
     * The new password
29
     * @var string $password
30
     */
31
    public $password;
32
33
    /**
34
     * The new password (again)
35
     * @var string $password_verify
36
     */
37
    public $password_verify;
38
39
    /**
40
     * The OTP code (optional)
41
     * @var string $otp
42
     */
43
    public $otp;
44
45
    /**
46
     * The user associated to the email
47
     * @var User $user
48
     */
49
    private $user = null;
50
51
    /**
52
     * Validation scenarios
53
     * @return array
54
     */
55
    public function scenarios()
56
    {
57
        return [
58
            self::SCENARIO_INIT => ['email'],
59
            self::SCENARIO_RESET => ['reset_token'],
60
        ];
61
    }
62
63
    /**
64
     * Validation rules
65
     * @return array
66
     */
67
    public function rules()
68
    {
69
        return [
70
            [['email'], 'required', 'on' => self::SCENARIO_INIT],
71
            [['email'], 'email', 'on' =>  self::SCENARIO_INIT],
72
            [['email'], 'validateUser', 'on' =>  self::SCENARIO_INIT],
73
74
            [['reset_token', 'password', 'password_verify'], 'required', 'on' => self::SCENARIO_RESET],
75
            [['reset_token'], 'validateResetToken', 'on' => self::SCENARIO_RESET],
76
            [['password', 'password_verify'], 'string', 'min' => 8, 'on' => self::SCENARIO_RESET],
77
            [['password_verify'], 'compare', 'compareAttribute' => 'password', 'on' => self::SCENARIO_RESET]
78
        ];
79
    }
80
    
81
    /**
82
     * Validates the users email
83
     * @inheritdoc
84
     */
85
    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...
86
    {
87
        if (!$this->hasErrors()) {
88
            $this->user = Yii::$app->yrc->userClass::findOne(['email' => $this->email]);
89
90
            if ($this->user === null) {
91
                $this->addError('email', Yii::t('yrc', 'The provided email address is not valid'));
92
            }
93
        }
94
    }
95
96
    /**
97
     * Reset token validator
98
     * @inheritdoc
99
     */
100
    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...
101
    {
102
        if (!$this->hasErrors()) {
103
            $tokenInfo = Yii::$app->cache->get(
104
                hash('sha256', $this->reset_token . '_reset_token')
105
            );
106
            
107
            if ($tokenInfo === null) {
108
                $this->addError('reset_token', Yii::t('yrc', 'The password reset token provided is not valid.'));
109
            }
110
111
            $this->user = Yii::$app->yrc->userClass::find()->where([
112
                'id' => $tokenInfo['id']
113
            ])->one();
114
115
            if ($this->user === null) {
116
                $this->addError('reset_token', Yii::t('yrc', 'The password reset token provided is not valid.'));
117
            } else {
118
                // If two factor authentication is enabled on the account, prevent it from being changed without a valid code
119
                if ($this->user->isOTPEnabled()) {
120
                    if ($this->otp === null || !$this->user->verifyOTP((string)$this->otp)) {
121
                        $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.'));
122
                    }
123
                }
124
            }
125
        }
126
    }
127
128
    /**
129
     * Sets the user object
130
     * @param User $user
131
     */
132
    public function setUser($user)
133
    {
134
        $this->user = $user;
135
        $this->email = $user->email;
136
    }
137
138
    /**
139
     * Changes the password for the user
140
     * @return boolean
141
     */
142
    public function reset()
143
    {
144
        if ($this->validate()) {
145
            if ($this->getScenario() === self::SCENARIO_INIT) {
146
                // Create an reset token for the user, and store it in the cache
147
                $token = Base32::encode(\random_bytes(64));
148
                
149
                Yii::$app->cache->set(hash('sha256', $token . '_reset_token'), [
150
                    'id' => $this->user->id
151
                ], strtotime(self::EXPIRY_TIME));
152
153
                return Yii::$app->yrc->sendEmail('password_reset', Yii::t('app', 'A request has been made to change your password'), $this->user->email, ['token' => $token]);
154
            } elseif ($this->getScenario() === self::SCENARIO_RESET) {
155
                $this->user->password = $this->password;
156
157
                if ($this->user->save()) {
158
                    return Yii::$app->yrc->sendEmail('password_change', Yii::t('app', 'Your password has been changed'), $this->email);
159
                }
160
            }
161
        }
162
163
        return false;
164
    }
165
}