User   A
last analyzed

Complexity

Total Complexity 30

Size/Duplication

Total Lines 303
Duplicated Lines 0 %

Coupling/Cohesion

Components 6
Dependencies 13

Test Coverage

Coverage 75.58%

Importance

Changes 0
Metric Value
wmc 30
lcom 6
cbo 13
dl 0
loc 303
ccs 65
cts 86
cp 0.7558
rs 10
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A behaviors() 0 16 2
A tableName() 0 4 1
A findIdentity() 0 4 1
A find() 0 4 1
A findIdentityByAccessToken() 0 4 1
A afterSave() 0 9 3
A attributeLabels() 0 16 1
A scenarios() 0 13 1
A rules() 0 36 1
A validateAuthKey() 0 4 1
A getId() 0 4 1
A getAuthKey() 0 4 1
A getIsBlocked() 0 4 1
A getIsAdmin() 0 4 1
A getIsConfirmed() 0 4 1
A hasRole() 0 4 1
A getProfile() 0 4 1
A getSocialNetworkAccounts() 0 18 3
A getPassword_age() 0 9 2
A beforeSave() 0 21 5
1
<?php
2
3
/*
4
 * This file is part of the 2amigos/yii2-usuario project.
5
 *
6
 * (c) 2amigOS! <http://2amigos.us/>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace Da\User\Model;
13
14
use Da\User\Helper\SecurityHelper;
15
use Da\User\Query\UserQuery;
16
use Da\User\Traits\ContainerAwareTrait;
17
use Da\User\Traits\ModuleAwareTrait;
18
use Yii;
19
use yii\base\Exception;
20
use yii\base\InvalidConfigException;
21
use yii\base\InvalidParamException;
22
use yii\base\NotSupportedException;
23
use yii\behaviors\TimestampBehavior;
24
use yii\db\ActiveRecord;
25
use yii\helpers\ArrayHelper;
26
use yii\web\Application;
27
use yii\web\IdentityInterface;
28
29
/**
30
 * User ActiveRecord model.
31
 *
32
 * @property bool $isAdmin
33
 * @property bool $isBlocked
34
 * @property bool $isConfirmed      whether user account has been confirmed or not
35
 * @property bool $gdpr_deleted     whether user requested deletion of his account
36
 * @property bool $gdpr_consent     whether user has consent personal data processing
37
 *
38
 * Database fields:
39
 * @property int $id
40
 * @property string $username
41
 * @property string $email
42
 * @property string $unconfirmed_email
43
 * @property string $password_hash
44
 * @property string $auth_key
45
 * @property string $auth_tf_key
46
 * @property int $auth_tf_enabled
47
 * @property string $registration_ip
48
 * @property int $confirmed_at
49
 * @property int $blocked_at
50
 * @property int $flags
51
 * @property int $created_at
52
 * @property int $updated_at
53
 * @property int $last_login_at
54
 * @property int $gdpr_consent_date date of agreement of data processing
55
 * @property string $last_login_ip
56
 * @property int $password_changed_at
57
 * @property int $password_age
58
 * Defined relations:
59
 * @property SocialNetworkAccount[] $socialNetworkAccounts
60
 * @property Profile $profile
61
 */
62
class User extends ActiveRecord implements IdentityInterface
63
{
64
    use ModuleAwareTrait;
65
    use ContainerAwareTrait;
66
67
    // following constants are used on secured email changing process
68
    const OLD_EMAIL_CONFIRMED = 0b01;
69
    const NEW_EMAIL_CONFIRMED = 0b10;
70
71
    /**
72
     * @var string Plain password. Used for model validation
73
     */
74
    public $password;
75
    /**
76
     * @var array connected account list
77
     */
78
    protected $connectedAccounts;
79
80
    /**
81
     * {@inheritdoc}
82
     *
83
     * @throws InvalidParamException
84
     * @throws InvalidConfigException
85
     * @throws Exception
86
     */
87 16
    public static function tableName()
88
    {
89 16
        return '{{%user}}';
90
    }
91
92
    /**
93
     * {@inheritdoc}
94
     */
95 3
    public static function findIdentity($id)
96
    {
97 3
        return static::findOne($id);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return static::findOne($id); (yii\db\ActiveRecordInterface|array|null) is incompatible with the return type declared by the interface yii\web\IdentityInterface::findIdentity of type yii\web\IdentityInterface|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
98
    }
99
100
    /**
101
     * @return UserQuery
102
     */
103 16
    public static function find()
104
    {
105 16
        return new UserQuery(static::class);
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     *
111
     * @throws NotSupportedException
112
     */
113
    public static function findIdentityByAccessToken($token, $type = null)
114
    {
115
        throw new NotSupportedException('Method "' . __CLASS__ . '::' . __METHOD__ . '" is not implemented.');
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121 10
    public function beforeSave($insert)
122
    {
123
        /** @var SecurityHelper $security */
124 10
        $security = $this->make(SecurityHelper::class);
125 10
        if ($insert) {
126 7
            $this->setAttribute('auth_key', $security->generateRandomString());
127 7
            if (Yii::$app instanceof Application) {
128 7
                $this->setAttribute('registration_ip', $this->module->disableIpLogging ? '127.0.0.1' : Yii::$app->request->getUserIP());
129
            }
130
        }
131
132 10
        if (!empty($this->password)) {
133 10
            $this->setAttribute(
134 10
                'password_hash',
135 10
                $security->generatePasswordHash($this->password, $this->getModule()->blowfishCost)
136
            );
137 10
            $this->password_changed_at = time();
138
        }
139
140 10
        return parent::beforeSave($insert);
141
    }
142
143
    /**
144
     * @inheritdoc
145
     *
146
     * @throws InvalidConfigException
147
     */
148 10
    public function afterSave($insert, $changedAttributes)
149
    {
150 10
        parent::afterSave($insert, $changedAttributes);
151
152 10
        if ($insert && $this->profile === null) {
153 7
            $profile = $this->make(Profile::class);
154 7
            $profile->link('user', $this);
155
        }
156 10
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161 16
    public function behaviors()
162
    {
163
        $behaviors = [
164 16
            TimestampBehavior::class,
165
        ];
166
167 16
        if ($this->module->enableGdprCompliance) {
168 4
            $behaviors['GDPR'] = [
169
                'class' => TimestampBehavior::class,
170
                'createdAtAttribute' => 'gdpr_consent_date',
171
                'updatedAtAttribute' => false
172
            ];
173
        }
174
175 16
        return $behaviors;
176
    }
177
178
    /**
179
     * {@inheritdoc}
180
     */
181 2
    public function attributeLabels()
182
    {
183
        return [
184 2
            'username' => Yii::t('usuario', 'Username'),
185 2
            'email' => Yii::t('usuario', 'Email'),
186 2
            'registration_ip' => Yii::t('usuario', 'Registration IP'),
187 2
            'unconfirmed_email' => Yii::t('usuario', 'New email'),
188 2
            'password' => Yii::t('usuario', 'Password'),
189 2
            'created_at' => Yii::t('usuario', 'Registration time'),
190 2
            'confirmed_at' => Yii::t('usuario', 'Confirmation time'),
191 2
            'last_login_at' => Yii::t('usuario', 'Last login time'),
192 2
            'last_login_ip' => Yii::t('usuario', 'Last login IP'),
193 2
            'password_changed_at' => Yii::t('usuario', 'Last password change'),
194 2
            'password_age' => Yii::t('usuario', 'Password age'),
195
        ];
196
    }
197
198
    /**
199
     * {@inheritdoc}
200
     */
201 9
    public function scenarios()
202
    {
203 9
        return ArrayHelper::merge(
204 9
            parent::scenarios(),
205
            [
206 9
                'register' => ['username', 'email', 'password'],
207
                'connect' => ['username', 'email'],
208
                'create' => ['username', 'email', 'password'],
209
                'update' => ['username', 'email', 'password'],
210
                'settings' => ['username', 'email', 'password'],
211
            ]
212
        );
213
    }
214
215
    /**
216
     * {@inheritdoc}
217
     */
218 9
    public function rules()
219
    {
220
        return [
221
            // username rules
222 9
            'usernameRequired' => ['username', 'required', 'on' => ['register', 'create', 'connect', 'update']],
223
            'usernameMatch' => ['username', 'match', 'pattern' => '/^[-a-zA-Z0-9_\.@\+]+$/'],
224
            'usernameLength' => ['username', 'string', 'min' => 3, 'max' => 255],
225
            'usernameTrim' => ['username', 'trim'],
226
            'usernameUnique' => [
227 9
                'username',
228 9
                'unique',
229 9
                'message' => Yii::t('usuario', 'This username has already been taken'),
230
            ],
231
232
            // email rules
233
            'emailRequired' => ['email', 'required', 'on' => ['register', 'connect', 'create', 'update']],
234
            'emailPattern' => ['email', 'email'],
235
            'emailLength' => ['email', 'string', 'max' => 255],
236
            'emailUnique' => [
237 9
                'email',
238 9
                'unique',
239 9
                'message' => Yii::t('usuario', 'This email address has already been taken'),
240
            ],
241
            'emailTrim' => ['email', 'trim'],
242
243
            // password rules
244
            'passwordTrim' => ['password', 'trim'],
245
            'passwordRequired' => ['password', 'required', 'on' => ['register']],
246
            'passwordLength' => ['password', 'string', 'min' => 6, 'max' => 72, 'on' => ['register', 'create']],
247
248
            // two factor auth rules
249
            'twoFactorSecretTrim' => ['auth_tf_key', 'trim'],
250
            'twoFactorSecretLength' => ['auth_tf_key', 'string', 'max' => 16],
251
            'twoFactorEnabledNumber' => ['auth_tf_enabled', 'boolean']
252
        ];
253
    }
254
255
    /**
256
     * {@inheritdoc}
257
     */
258
    public function validateAuthKey($authKey)
259
    {
260
        return $this->getAttribute('auth_key') === $authKey;
261
    }
262
263
    /**
264
     * {@inheritdoc}
265
     */
266 9
    public function getId()
267
    {
268 9
        return $this->getAttribute('id');
269
    }
270
271
    /**
272
     * {@inheritdoc}
273
     */
274 9
    public function getAuthKey()
275
    {
276 9
        return $this->getAttribute('auth_key');
277
    }
278
279
    /**
280
     * @return bool whether is blocked or not
281
     */
282 4
    public function getIsBlocked()
283
    {
284 4
        return $this->blocked_at !== null;
285
    }
286
287
    /**
288
     * @throws InvalidConfigException
289
     * @return bool                   whether the user is an admin or not
290
     */
291 2
    public function getIsAdmin()
292
    {
293 2
        return $this->getAuth()->isAdmin($this->username);
294
    }
295
296
    /**
297
     * Returns whether user account has been confirmed or not.
298
     * @return bool whether user account has been confirmed or not
299
     */
300 5
    public function getIsConfirmed()
301
    {
302 5
        return $this->confirmed_at !== null;
303
    }
304
305
    /**
306
     * Checks whether a user has a specific role.
307
     *
308
     * @param string $role
309
     *
310
     * @return bool
311
     */
312
    public function hasRole($role)
313
    {
314
        return $this->getAuth()->hasRole($this->id, $role);
315
    }
316
317
    /**
318
     * @throws InvalidConfigException
319
     * @throws InvalidParamException
320
     * @return \yii\db\ActiveQuery
321
     */
322 10
    public function getProfile()
323
    {
324 10
        return $this->hasOne($this->getClassMap()->get(Profile::class), ['user_id' => 'id']);
325
    }
326
327
    /**
328
     * @throws \Exception
329
     * @return SocialNetworkAccount[] social connected accounts [ 'providerName' => socialAccountModel ]
330
     *
331
     */
332
    public function getSocialNetworkAccounts()
333
    {
334
        if (null === $this->connectedAccounts) {
335
            /** @var SocialNetworkAccount[] $accounts */
336
            $accounts = $this->hasMany(
337
                $this->getClassMap()
338
                    ->get(SocialNetworkAccount::class),
339
                ['user_id' => 'id']
340
            )
341
                ->all();
342
343
            foreach ($accounts as $account) {
344
                $this->connectedAccounts[$account->provider] = $account;
345
            }
346
        }
347
348
        return $this->connectedAccounts;
349
    }
350
351
    /**
352
     * Returns password age in days
353
     * @return integer
354
     */
355
    public function getPassword_age()
356
    {
357
        if (is_null($this->password_changed_at)) {
358
            return $this->getModule()->maxPasswordAge;
359
        }
360
        $d = new \DateTime("@{$this->password_changed_at}");
361
362
        return $d->diff(new \DateTime(), true)->format("%a");
363
    }
364
}
365