Issues (73)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

models/User.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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