User::setFlows()   B
last analyzed

Complexity

Conditions 8
Paths 7

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 35
rs 8.1155
c 0
b 0
f 0
cc 8
nc 7
nop 1
1
<?php
2
3
namespace app\models;
4
5
use Yii;
6
use yii\db\Expression;
7
8
/**
9
 * This is the model class for table "user".
10
 *
11
 * @property string $username
12
 * @property string $password
13
 * @property string $hash
14
 * @property string $authkey
15
 * @property string $access_token
16
 * @property string $added_at
17
 * @property string $last_login_at
18
 * @property bool $fromLdap
19
 * @property bool $remember_me
20
 * @property UserHasFlow[] $userHasFlows
21
 * @property Flow[] $flows
22
 * @property \yii\rbac\Role $role
23
 * @property string $roleName
24
 */
25
class User extends \yii\db\ActiveRecord implements \yii\web\IdentityInterface
26
{
27
    public $password;
28
    public $fromLdap = false;
29
30
    /**
31
     * {@inheritdoc}
32
     */
33
    public static function tableName()
34
    {
35
        return 'user';
36
    }
37
38
    /**
39
     * {@inheritdoc}
40
     */
41
    public function rules()
42
    {
43
        return [
44
            [['username'], 'required'],
45
            [['added_at', 'last_login_at', 'roleName', 'flows'], 'safe'],
46
            [['username', 'hash', 'authkey', 'access_token'], 'string', 'max' => 64],
47
        ];
48
    }
49
50
    public function attributeLabels()
51
    {
52
        return [
53
            'username' => Yii::t('app', 'Username'),
54
            'hash' => Yii::t('app', 'Hash'),
55
            'authkey' => Yii::t('app', 'Authkey'),
56
            'access_token' => Yii::t('app', 'Access token'),
57
            'added_at' => Yii::t('app', 'Added at'),
58
            'last_login_at' => Yii::t('app', 'Last login at'),
59
            'roleName' => Yii::t('app', 'Role'),
60
            'flows' => Yii::t('app', 'Flows'),
61
        ];
62
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67
    public static function findIdentity($id)
68
    {
69
        $user = static::findOne($id);
70
        if (!$user) {
71
            $user = self::findInLdap($id);
72
        }
73
74
        return $user;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $user; (null|yii\db\ActiveRecordInterface|array) is incompatible with the return type declared by the interface yii\web\IdentityInterface::findIdentity of type yii\web\IdentityInterface.

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...
75
    }
76
77
    /**
78
     * {@inheritdoc}
79
     */
80
    public static function findIdentityByAccessToken($token, $type = null)
81
    {
82
        return static::findOne(['access_token' => $token]);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return static::findOne(a...ess_token' => $token)); (yii\db\ActiveRecordInterface|array|null) is incompatible with the return type declared by the interface yii\web\IdentityInterfac...ndIdentityByAccessToken of type yii\web\IdentityInterface.

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...
83
    }
84
85
    /**
86
     * {@inheritdoc}
87
     */
88
    public function getId()
89
    {
90
        return $this->username;
91
    }
92
93
    /**
94
     * {@inheritdoc}
95
     */
96
    public function getAuthKey()
97
    {
98
        return $this->authkey;
99
    }
100
101
    /**
102
     * Create a used with specified username and password.
103
     *
104
     * @param string $username
105
     * @param string $password
106
     *
107
     * @return User|null created user
108
     */
109
    public static function create($username, $password)
110
    {
111
        $user = new self();
112
        $user->username = $username;
113
        $user->password = $password;
114
115
        return $user->save() ? $user : null;
116
    }
117
118
    /**
119
     * Authenticate user with password on LDAP is possible or in DB.
120
     *
121
     * @param string $password
122
     *
123
     * @return bool is authenticated
124
     */
125
    public function authenticate($password)
126
    {
127
        $this->password = $password;
128
        if (Yii::$app->params['useLdap'] && Yii::$app->ldap->auth()->attempt($this->getId(), $password)) {
129
            $this->fromLdap = true;
130
131
            return true;
132
        }
133
134
        return $this->validatePassword($password);
135
    }
136
137
    /**
138
     * Look for a user ID in LDAP.
139
     *
140
     * @param string $id user ID
141
     *
142
     * @return User|null found user
143
     */
144
    public static function findInLdap($id)
145
    {
146
        if (Yii::$app->params['useLdap']) {
147
            $ldapUser = Yii::$app->ldap->search()->users()->findBy(Yii::$app->params['activeDirectorySchema'] ? 'samAccountName' : 'uid', $id);
148
            if ($ldapUser) {
149
                $user = new self();
150
                $user->username = $id;
151
                $user->fromLdap = true;
152
153
                return $user;
154
            }
155
        }
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161
    public function validateAuthKey($authkey)
162
    {
163
        return $this->getAuthkey() === $authkey;
164
    }
165
166
    /**
167
     * Validates password.
168
     *
169
     * @param string $password password to validate
170
     *
171
     * @return bool if password provided is valid for current user
172
     */
173
    public function validatePassword($password)
174
    {
175
        if (!$this->hash) {
176
            return false;
177
        }
178
179
        return Yii::$app->security->validatePassword($password, $this->hash);
180
    }
181
182
    /**
183
     * After find event
184
     * Update user to indicated LDAP or DB source.
185
     */
186
    public function afterFind()
187
    {
188
        parent::afterFind();
189
        if ($this->hash === null) {
190
            $this->fromLdap = true;
191
        }
192
    }
193
194
    /**
195
     * Before save event
196
     * Create authkey and access token is newly created
197
     * Also hash password if not comming from LDAP.
198
     *
199
     * @param bool $insert is model inserted
200
     *
201
     * @return bool success
202
     */
203
    public function beforeSave($insert)
204
    {
205
        if (parent::beforeSave($insert)) {
206
            if ($this->isNewRecord) {
207
                $this->authkey = Yii::$app->security->generateRandomString();
208
                $this->access_token = Yii::$app->security->generateRandomString();
209
                if (!$this->fromLdap && $this->password) {
210
                    $this->hash = Yii::$app->security->generatePasswordHash($this->password);
211
                    $this->password = null;
212
                }
213
            }
214
215
            return true;
216
        }
217
218
        return false;
219
    }
220
221
    /**
222
     * After login event
223
     * Update last_login_at field to indicate login timestamp.
224
     *
225
     * @param mixed $event user event
226
     */
227
    public static function afterLogin($event)
228
    {
229
        $event->identity->last_login_at = new Expression('NOW()');
230
231
        $event->identity->save();
232
    }
233
234
    /**
235
     * @return \yii\db\ActiveQuery
236
     */
237
    public function getUserHasFlows()
238
    {
239
        return $this->hasMany(UserHasFlow::class, ['user_username' => 'username']);
240
    }
241
242
    /**
243
     * @return \yii\db\ActiveQuery
244
     */
245
    public function getFlows()
246
    {
247
        return $this->hasMany(Flow::class, ['id' => 'flow_id'])->viaTable('user_has_flow', ['user_username' => 'username']);
248
    }
249
250
    /**
251
     * Update usable flows for user from form
252
     * Link and unlink based on new & old flow id arrays.
253
     *
254
     * @param array $flows flows ids
255
     */
256
    public function setFlows($flows)
257
    {
258
        if ($flows === '' || count($flows) === 0) {
259
            $this->unlinkAll('flows', true);
260
261
            return true;
262
        }
263
264
        $prevFlows = array_map(function ($f) {
265
            return $f->id;
266
        }, $this->flows);
267
268
        $unlink = array_diff($prevFlows, $flows);
269
        if (count($unlink)) {
270
            $uFlows = Flow::findAll($unlink);
271
            foreach ($uFlows as $f) {
272
                $this->unlink('flows', $f, true);
273
            }
274
        }
275
276
        $link = array_diff($flows, $prevFlows);
277
        if (count($link)) {
278
            $lFlows = Flow::findAll($link);
279
            if (count($lFlows) != count($link)) {
280
                $this->addError('flows', Yii::t('app', 'Trying to add non-existing flow'));
281
282
                return false;
283
            }
284
            foreach ($lFlows as $f) {
285
                $this->link('flows', $f);
286
            }
287
        }
288
289
        return true;
290
    }
291
292
    /**
293
     * Retrieve user role from authmanager.
294
     *
295
     * @return \yii\rbac\Role|null first role
296
     */
297
    public function getRole()
298
    {
299
        $roles = Yii::$app->authManager->getRolesByUser($this->getId());
300
        $roleNames = array_keys($roles);
301
302
        return count($roleNames) ? $roles[$roleNames[0]] : null;
303
    }
304
305
    /**
306
     * Retrieve user role from authmanager.
307
     *
308
     * @return string|null first role name
309
     */
310
    public function getRoleName()
311
    {
312
        $role = $this->role;
313
314
        return $role ? $role->name : null;
315
    }
316
317
    /**
318
     * Set user role in authmanager.
319
     *
320
     * @param string $roleName
321
     *
322
     * @return bool success
323
     */
324
    public function setRoleName($roleName)
325
    {
326
        $auth = Yii::$app->authManager;
327
        $auth->revokeAll($this->getId());
328
        if (($r = $auth->getRole($roleName)) !== null) {
329
            return $auth->assign($r, $this->getId()) !== null;
330
        }
331
332
        return false;
333
    }
334
335
    /**
336
     * Indicates if we have to display flows with associated role.
337
     *
338
     * @return bool need to display flow
339
     */
340
    public function getNeedsFlow()
341
    {
342
        $role = $this->role;
343
344
        return $role && $role->data && array_key_exists('requireFlow', $role->data);
345
    }
346
}
347