Issues (9)

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.

src/Behavior.php (5 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
 * @link https://github.com/vuongxuongminh/yii2-mfa
4
 * @copyright Copyright (c) 2019 Vuong Xuong Minh
5
 * @license [New BSD License](http://www.opensource.org/licenses/bsd-license.php)
6
 */
7
8
namespace vxm\mfa;
9
10
use Yii;
11
12
use yii\base\Behavior as BaseBehavior;
13
use yii\base\InvalidValueException;
14
use yii\di\Instance;
15
use yii\web\User;
16
use yii\web\UserEvent;
17
use yii\web\ForbiddenHttpException;
18
19
20
/**
21
 * Class MfaBehavior automatically redirect to verify mfa url when identity enabled it and verify digits given.
22
 *
23
 * To use MfaBehavior, configure the [[User::$identityClass]] property which should specify class implemented [[\vxm\mfa\IdentityInterface]].
24
 *
25
 * For example,
26
 *
27
 * ```php
28
 *
29
 * use yii\db\ActiveRecord;
30
 *
31
 * use vxm\mfa\IdentityInterface;
32
 *
33
 * class Identity extends ActiveRecord implements IdentityInterface {
34
 *
35
 *          public function getMfaSecretKey()
36
 *          {
37
 *              return $this->mfa_secret;
38
 *          }
39
 *
40
 * }
41
 * ```
42
 *
43
 * And attach this behavior to the [[User]] component of an application config.
44
 *
45
 * For example,
46
 *
47
 * ```php
48
 *
49
 * 'user' => [
50
 *      'as mfa' => [
51
 *           'class' => 'vxm\mfa\MfaBehavior',
52
 *           'verifyUrl' => 'site/mfa-verify',
53
 *      ]
54
 *
55
 * ]
56
 * ```
57
 *
58
 * Note: it only work when an owner [[User::$enableSession]] is true.
59
 *
60
 * @property Otp $otp use to validate, generating an otp digits.
61
 *
62
 * @author Vuong Minh <[email protected]>
63
 * @since 1.0.0
64
 */
65
class Behavior extends BaseBehavior
66
{
67
68
    /**
69
     * @var User
70
     */
71
    public $owner;
72
73
    /**
74
     * @var callable|bool weather enabling this behavior. This property use in special case you need to disable it in runtime environment.
75
     * When it is callable this object instance will be parse to first parameter.
76
     *
77
     * Example:
78
     * ```php
79
     * function(\vxm\mfa\Behavior $behavior) {
80
     *
81
     *
82
     * }
83
     * ```
84
     */
85
    public $enable = true;
86
87
    /**
88
     * @var string|array the URL for login when [[verifyRequired()]] is called.
89
     * If an array is given, [[\yii\web\UrlManager::createUrl()]] will be called to create the corresponding URL.
90
     * The first element of the array should be the route to the verify action, and the rest of
91
     * the name-value pairs are GET parameters used to construct the verify URL. For example,
92
     *
93
     * ```php
94
     * 'site/mfa-verify'
95
     * ```
96
     *
97
     * If this property is `null`, a 403 HTTP exception will be raised when [[verifyRequired()]] is called.
98
     */
99
    public $verifyUrl;
100
101
    /**
102
     * @var string the session variable name used to store values of an identity logged in.
103
     */
104
    public $mfaParam = '__mfa';
105
106
    /**
107
     * @inheritDoc
108
     */
109 13
    public function init()
110
    {
111 13
        if (is_callable($this->enable)) {
112
            $this->enable = call_user_func($this->enable, $this);
113
        }
114
115 13
        parent::init();
116 13
    }
117
118
    /**
119
     * @inheritDoc
120
     */
121 13
    public function events()
122
    {
123 13
        if ($this->enable) {
124
            return [
125 13
                User::EVENT_BEFORE_LOGIN => 'beforeLogin'
126
            ];
127
        } else {
128
            return [];
129
        }
130
    }
131
132
    /**
133
     * Event trigger when before user log in to system. It will be require an user verify otp digits except when user logged in via cookie base.
134
     *
135
     * @param UserEvent $event an event triggered
136
     * @throws ForbiddenHttpException
137
     */
138 11
    public function beforeLogin(UserEvent $event)
139
    {
140 11
        if (!$event->isValid) {
141
            return;
142
        }
143
144 11
        if (!$event->identity instanceof IdentityInterface) {
145
            throw new InvalidValueException("{$this->owner->identityClass}::findIdentity() must return an object implementing \\vxm\\mfa\\IdentityInterface.");
146
        }
147
148 11
        $secretKey = $event->identity->getMfaSecretKey();
149
150 11
        if (!empty($secretKey) && $this->owner->enableSession && !$event->cookieBased) {
151 11
            $event->isValid = false;
152 11
            $this->saveIdentityLoggedIn($event->identity, $event->duration);
153 11
            $this->verifyRequired();
154
        }
155 11
    }
156
157
    /**
158
     * Switches to a logged in identity for the current user.
159
     *
160
     * @see \yii\web\User::switchIdentity()
161
     */
162 5
    public function switchIdentityLoggedIn()
163
    {
164 5
        $data = $this->getIdentityLoggedIn();
165
166 5
        if ($data === null) {
167
            return;
168
        }
169
170 5
        list($identity, $duration) = $data;
171 5
        $this->owner->switchIdentity($identity, $duration);
172 5
    }
173
174
    /**
175
     * Save the user identity logged in object when an identity need to verify.
176
     *
177
     * @param IdentityInterface|null $identity the identity object associated with the currently logged user.
178
     * @param int $duration number of seconds that the user can remain in logged-in status.
179
     */
180 11
    public function saveIdentityLoggedIn(IdentityInterface $identity, int $duration)
181
    {
182 11
        Yii::$app->getSession()->set($this->mfaParam, [$identity->getId(), $duration]);
0 ignored issues
show
The method getSession does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
183 11
    }
184
185
    /**
186
     * Get an identity logged in.
187
     *
188
     * @return array|null Returns an array of 'identity' and 'duration' if valid, otherwise null.
189
     * @see saveIdentityLoggedIn()
190
     */
191 12
    public function getIdentityLoggedIn()
192
    {
193 12
        $data = Yii::$app->getSession()->get($this->mfaParam);
0 ignored issues
show
The method getSession does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
194
195 12
        if ($data === null) {
196 2
            return null;
197
        }
198
199 11
        if (is_array($data) && count($data) == 2) {
200 11
            list($id, $duration) = $data;
201
            /* @var $class IdentityInterface */
202 11
            $class = $this->owner->identityClass;
203 11
            $identity = $class::findIdentity($id);
204
205 11
            if ($identity !== null) {
206 11
                if (!$identity instanceof IdentityInterface) {
207
                    throw new InvalidValueException("$class::findIdentity() must return an object implementing \\vxm\\mfa\\IdentityInterface.");
208
                } else {
209 11
                    return [$identity, $duration];
210
                }
211
            }
212
        }
213
214
        $this->removeIdentityLoggedIn();
215
216
        return null;
217
    }
218
219
    /**
220
     * Removes the identity logged in.
221
     */
222 2
    public function removeIdentityLoggedIn()
223
    {
224 2
        Yii::$app->getSession()->remove($this->mfaParam);
0 ignored issues
show
The method getSession does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
225 2
    }
226
227
    /**
228
     * @var Otp|null an otp instance use to generate and validate otp.
229
     */
230
    private $_otp;
231
232
    /**
233
     * Get an otp instance.
234
     *
235
     * @return Otp|null an otp instance use to generate and validate otp.
236
     * @throws \yii\base\InvalidConfigException
237
     */
238 8
    public function getOtp()
239
    {
240 8
        if ($this->_otp === null) {
241 7
            $this->setOtp(Otp::class);
242
        }
243
244 8
        return $this->_otp;
245
    }
246
247
    /**
248
     * Set an otp instance use to generate and validate otp.
249
     *
250
     * @param array|string|Otp $otp object instance
251
     * @throws \yii\base\InvalidConfigException
252
     */
253 8
    public function setOtp($otp)
254
    {
255 8
        if (is_array($otp) && !isset($otp['class'])) {
256 1
            $otp['class'] = Otp::class;
257
        }
258
259 8
        $this->_otp = Instance::ensure($otp, Otp::class);
260 8
    }
261
262
    /**
263
     * Generate an otp by current user logged in
264
     *
265
     * @return string|null an otp of current user logged in.
266
     * @throws \yii\base\InvalidConfigException
267
     * @throws \yii\base\NotSupportedException
268
     */
269 4 View Code Duplication
    public function generateOtpByIdentityLoggedIn()
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
270
    {
271 4
        $data = $this->getIdentityLoggedIn();
272
273 4
        if (is_array($data)) {
274
275
            /** @var IdentityInterface $identity */
276 4
            $identity = $data[0];
277 4
            $secretKey = $identity->getMfaSecretKey();
278
279 4
            if (!empty($secretKey)) {
280 4
                return $this->getOtp()->generate($secretKey);
281
            }
282
        }
283
284
        return null;
285
    }
286
287
    /**
288
     * Validate an otp by current user logged in
289
     *
290
     * @param string $otp need to be validate
291
     * @return bool weather an otp given is valid with identity logged in
292
     * @throws \yii\base\InvalidConfigException
293
     * @throws \yii\base\NotSupportedException
294
     */
295 3 View Code Duplication
    public function validateOtpByIdentityLoggedIn(string $otp)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
296
    {
297 3
        $data = $this->getIdentityLoggedIn();
298
299 3
        if (is_array($data)) {
300
            /** @var IdentityInterface $identity */
301 3
            $identity = $data[0];
302 3
            $secretKey = $identity->getMfaSecretKey();
303
304 3
            if (!empty($secretKey)) {
305 3
                return $this->getOtp()->validate($secretKey, $otp);
306
            }
307
        }
308
309
        return false;
310
    }
311
312
    /**
313
     * Return a qr code uri of current user
314
     *
315
     * @param array $params list of information use to show on an authenticator app.
316
     *
317
     * Example:
318
     * ```php
319
     * ['issuer' => 'VXM', 'label' => '[email protected]', 'image' => 'https://google.com']
320
     * ```
321
     *
322
     * @return string|null qr code uri. If `null`, it means the user is a guest or not enable mfa.
323
     * @throws \Throwable
324
     */
325 4
    public function getQrCodeUri(array $params)
326
    {
327 4
        if ($identity = $this->owner->getIdentity()) {
328 3
            if (!$identity instanceof IdentityInterface) {
329
                throw new InvalidValueException("{$this->owner->identityClass}::findIdentity() must return an object implementing \\vxm\\mfa\\IdentityInterface.");
330
            } else {
331 3
                $secretKey = $identity->getMfaSecretKey();
332
333 3
                if (!empty($secretKey)) {
334 3
                    return $this->getOtp()->getQrCodeUri($secretKey, $params);
335
                }
336
            }
337
        }
338
339 1
        return null;
340
    }
341
342
    /**
343
     * Redirects the user browser to the mfa verify page..
344
     *
345
     * Make sure you set [[verifyUrl]] so that the user browser can be redirected to the specified verify URL after
346
     * calling this method.
347
     *
348
     * Note that when [[verifyUrl]] is set, calling this method will NOT terminate the application execution.
349
     *
350
     * @return \yii\web\Response the redirection response if [[verifyUrl]] is set
351
     * @throws ForbiddenHttpException
352
     */
353 11
    protected function verifyRequired()
354
    {
355 11
        if ($this->verifyUrl !== null) {
356 11
            $verifyUrl = (array)$this->verifyUrl;
357
358 11
            if ($verifyUrl[0] !== Yii::$app->requestedRoute) {
359 11
                return Yii::$app->getResponse()->redirect($this->verifyUrl);
360
            }
361
        }
362
363
        throw new ForbiddenHttpException(Yii::t('app', 'Mfa verify required!'));
364
    }
365
366
}
367