Passed
Branch master (b11977)
by Charles
02:33
created

User   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 374
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 374
rs 8.295
c 0
b 0
f 0
wmc 42

25 Methods

Rating   Name   Duplication   Size   Complexity  
A validatePassword() 0 16 4
A rules() 0 11 1
A provisionOTP() 0 23 3
A activate() 0 4 1
A findIdentityByAccessToken() 0 8 2
A enableOTP() 0 13 3
A getToken() 0 3 1
A saveAllowance() 0 9 1
A disableOTP() 0 6 1
A beforeSave() 0 11 4
A behaviors() 0 4 1
A setToken() 0 7 2
A loadAllowance() 0 13 2
A init() 0 13 4
A validateAuthKey() 0 3 1
A findIdentity() 0 3 1
A getRateLimit() 0 5 1
A getId() 0 3 1
A verifyOTP() 0 12 1
A isActivated() 0 3 1
A beforeValidate() 0 8 2
A isOTPEnabled() 0 3 1
A attributeLabels() 0 13 1
A getAuthKey() 0 2 1
A tableName() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like User often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use User, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace yrc\models;
4
5
use Base32\Base32;
0 ignored issues
show
Bug introduced by
The type Base32\Base32 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...
6
use OTPHP\TOTP;
0 ignored issues
show
Bug introduced by
The type OTPHP\TOTP 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...
7
8
use yii\behaviors\TimestampBehavior;
0 ignored issues
show
Bug introduced by
The type yii\behaviors\TimestampBehavior 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...
9
use yii\db\ActiveRecord;
0 ignored issues
show
Bug introduced by
The type yii\db\ActiveRecord 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...
10
use yii\filters\RateLimitInterface;
0 ignored issues
show
Bug introduced by
The type yii\filters\RateLimitInterface 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...
11
use yii\web\IdentityInterface;
0 ignored issues
show
Bug introduced by
The type yii\web\IdentityInterface 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...
12
use Yii;
0 ignored issues
show
Bug introduced by
The type Yii 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...
13
14
/**
15
 * This is the model class for table "user".
16
 *
17
 * @property integer $id
18
 * @property string $email
19
 * @property string $username
20
 * @property string $password
21
 * @property string $activation_token
22
 * @property string $reset_token
23
 * @property integer $verified
24
 * @property string $otp_secret
25
 * @property string $otp_enabled
26
 * @property integer $created_at
27
 * @property integer $updated_at
28
 */
29
abstract class User extends ActiveRecord implements IdentityInterface, RateLimitInterface
30
{
31
    /**
32
     * password_hash Algorithm
33
     * @var integer
34
     */
35
    private $passwordHashAlgorithm = PASSWORD_BCRYPT;
36
    
37
    /**
38
     * The rate limit
39
     * @var integer
40
     */
41
    private $rateLimit = 150;
42
43
    /**
44
     * The rate limit window
45
     * @var integer
46
     */
47
    private $rateLimitWindow = 900;
48
    
49
    /**
50
     * password_hash options
51
     * @var array
52
     */
53
    private $passwordHashOptions = [
54
        'cost' => 13,
55
        'memory_cost' => 1<<12,
56
        'time_cost' => 3,
57
        'threads' => 1
58
    ];
59
    
60
    /**
61
     * The token used to authenticate the user
62
     * @var app\models\Token
0 ignored issues
show
Bug introduced by
The type yrc\models\app\models\Token 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 $token;
65
66
    /**
67
     * Sets the token used to authenticate the user
68
     * @return app\models\Token
69
     */
70
    public function getToken()
71
    {
72
        return $this->token;
73
    }
74
75
    /**
76
     * Sets the token that was used to authenticate the user
77
     */
78
    public function setToken($token)
79
    {
80
        if ($this->token !== null) {
81
            throw new \yii\base\Exception(Yii::t('yrc', 'The user has already been authenticated.'));
0 ignored issues
show
Bug introduced by
The type yii\base\Exception 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...
82
        }
83
84
        $this->token = $token;
85
    }
86
87
    /**
88
     * Overrides init
89
     */
90
    public function init()
91
    {
92
        // self init
93
        parent::init();
94
95
        // Prefer Argon2 if it is available, but fall back to BCRYPT if it isn't
96
        if (defined('PASSWORD_ARGON2I')) {
97
            $this->passwordHashAlgorithm = PASSWORD_ARGON2I;
98
        }
99
100
        // Lower the bcrypt cost when running tests
101
        if (YII_DEBUG && $this->passwordHashAlgorithm === PASSWORD_BCRYPT) {
0 ignored issues
show
Bug introduced by
The constant yrc\models\YII_DEBUG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
102
            $this->passwordHashOptions['cost'] = 10;
103
        }
104
    }
105
106
    /**
107
     * @inheritdoc
108
     */
109
    public function behaviors()
110
    {
111
        return [
112
            TimestampBehavior::class,
113
        ];
114
    }
115
116
    /**
117
     * @inheritdoc
118
     */
119
    public function getRateLimit($request, $action)
120
    {
121
        return [
122
            $this->rateLimit,
123
            $this->rateLimitWindow
124
        ];
125
    }
126
127
    /**
128
     * @inheritdoc
129
     */
130
    public function loadAllowance($request, $action)
131
    {
132
        $hash = Yii::$app->user->id . $request->getUrl() . $action->id;
133
        $allowance = Yii::$app->cache->get($hash);
134
135
        if ($allowance === false) {
136
            return [
137
                $this->rateLimit,
138
                time()
139
            ];
140
        }
141
142
        return $allowance;
143
    }
144
145
    /**
146
     * @inheritdoc
147
     */
148
    public function saveAllowance($request, $action, $allowance, $timestamp)
149
    {
150
        $hash = Yii::$app->user->id . $request->getUrl() . $action->id;
151
        $allowance = [
152
            $allowance,
153
            $timestamp
154
        ];
155
156
        Yii::$app->cache->set($hash, $allowance, $this->rateLimitWindow);
157
    }
158
    
159
    /**
160
     * @inheritdoc
161
     */
162
    public static function tableName()
163
    {
164
        return 'user';
165
    }
166
167
    /**
168
     * @inheritdoc
169
     */
170
    public function rules()
171
    {
172
        return [
173
            [['password', 'email', 'username'], 'required'],
174
            [['email'], 'email'],
175
            [['password'], 'string', 'length' => [8, 100]],
176
            [['username'], 'string', 'length' => [1, 100]],
177
            [['created_at', 'updated_at', 'otp_enabled', 'verified'], 'integer'],
178
            [['password', 'email'], 'string', 'max' => 255],
179
            [['email'], 'unique'],
180
            [['username'], 'unique'],
181
        ];
182
    }
183
184
    /**
185
     * @inheritdoc
186
     */
187
    public function attributeLabels()
188
    {
189
        return [
190
            'id'                => 'ID',
191
            'email'             => 'Email Address',
192
            'username'          => 'Username',
193
            'password'          => 'Password',
194
            'activation_token'  => 'Activation Token',
195
            'otp_secret'        => 'One Time Password Secret Value',
196
            'otp_enabled'       => 'Is Two Factor Authentication Enabled?',
197
            'verified'          => 'Is the account email verified?',
198
            'created_at'        => 'Created At',
199
            'updated_at'        => 'Last Updated At'
200
        ];
201
    }
202
203
    public function beforeValidate()
204
    {
205
        if (parent::beforeValidate()) {
206
            $this->username = \strtolower($this->username);
207
            return true;
208
        }
209
210
        return false;
211
    }
212
213
    /**
214
     * Before save occurs
215
     * @return bool
216
     */
217
    public function beforeSave($insert)
218
    {
219
        if (parent::beforeSave($insert)) {
220
            if ($this->isNewRecord || $this->password !== $this->oldAttributes['password']) {
221
                $this->password = password_hash($this->password, $this->passwordHashAlgorithm, $this->passwordHashOptions);
222
            }
223
            
224
            return true;
225
        }
226
        
227
        return false;
228
    }
229
    
230
    /**
231
     * Validates the user's password
232
     * @param string $password
233
     * return bool
234
     */
235
    public function validatePassword($password)
236
    {
237
        if (password_verify($password, $this->password)) {
238
            if (password_needs_rehash($this->password, $this->passwordHashAlgorithm, $this->passwordHashOptions)) {
239
                $this->password = password_hash($password, $this->passwordHashAlgorithm, $this->passwordHashOptions);
240
                
241
                // Allow authentication to continue if we weren't able to update the password, but log the message
242
                if (!$this->save()) {
243
                    Yii::warning('Unable to save newly hashed password for user: ' . $this->id);
244
                }
245
            }
246
247
            return true;
248
        }
249
        
250
        return false;
251
    }
252
253
    /**
254
     * Returns true of OTP is enabled
255
     * @return boolean
256
     */
257
    public function isOTPEnabled()
258
    {
259
        return (bool)$this->otp_enabled;
260
    }
261
    
262
    /**
263
     * Provisions TOTP for the account
264
     * @return boolean|string
265
     */
266
    public function provisionOTP()
267
    {
268
        if ($this->isOTPEnabled() === true) {
269
            return false;
270
        }
271
272
        $secret = \random_bytes(256);
273
        $encodedSecret = Base32::encode($secret);
274
        $totp = TOTP::create(
275
            $encodedSecret,
276
            30,             // 30 second window
277
            'sha256',       // SHA256 for the hashing algorithm
278
            6               // 6 digits
279
        );
280
        $totp->setLabel($this->username);
281
282
        $this->otp_secret = $encodedSecret;
283
284
        if ($this->save()) {
285
            return $totp->getProvisioningUri();
286
        }
287
288
        return false;
289
    }
290
291
    /**
292
     * Enables OTP
293
     * @return boolean
294
     */
295
    public function enableOTP()
296
    {
297
        if ($this->isOTPEnabled() === true) {
298
            return true;
299
        }
300
301
        if ($this->otp_secret == "") {
302
            return false;
303
        }
304
        
305
        $this->otp_enabled = 1;
306
307
        return $this->save();
308
    }
309
310
    /**
311
     * Disables OTP
312
     * @return boolean
313
     */
314
    public function disableOTP()
315
    {
316
        $this->otp_secret = "";
317
        $this->otp_enabled = 0;
318
319
        return $this->save();
320
    }
321
322
    /**
323
     * Verifies the OTP code
324
     * @param integer $code
325
     * @return boolean
326
     */
327
    public function verifyOTP($code)
328
    {
329
        $totp = TOTP::create(
330
            $this->otp_secret,
331
            30,             // 30 second window
332
            'sha256',       // SHA256 for the hashing algorithm
333
            6               // 6 digits
334
        );
335
336
        $totp->setLabel($this->username);
337
338
        return $totp->verify($code);
339
    }
340
341
    /**
342
     * Activates the user
343
     * @return boolean
344
     */
345
    public function activate()
346
    {
347
        $this->verified = 1;
348
        return $this->save();
349
    }
350
351
    /**
352
     * Whether or not a user is activated or not
353
     * @return boolean
354
     */
355
    public function isActivated()
356
    {
357
        return (bool)$this->verified;
358
    }
359
360
    /**
361
     * @inheritdoc
362
     */
363
    public static function findIdentity($id)
364
    {
365
        return static::findOne($id);
366
    }
367
    
368
    /**
369
     * @inheritdoc
370
     */
371
    public static function findIdentityByAccessToken($token, $type = null)
372
    {
373
        // Checking of the Token is performed in app\components\filters\auth\HMACSignatureAuth
374
        if ($token === null) {
375
            return null;
376
        }
377
        
378
        return static::find()->where(['id' => $token->user_id])->one();
379
    }
380
381
    /**
382
     * @inheritdoc
383
     */
384
    public function getAuthKey()
385
    {
386
        
387
    }
388
389
    /**
390
     * @inheritdoc
391
     */
392
    public function validateAuthKey($authKey)
393
    {
394
        return true;
395
    }
396
397
    /**
398
     * @inheritdoc
399
     */
400
    public function getId()
401
    {
402
        return $this->id;
403
    }
404
}
405