TotpController   A
last analyzed

Complexity

Total Complexity 38

Size/Duplication

Total Lines 228
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 38
lcom 1
cbo 3
dl 0
loc 228
ccs 0
cts 104
cp 0
rs 9.36
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A denyCallback() 0 4 1
B actionDisable() 0 22 6
A behaviors() 0 42 1
B actionEnable() 0 35 10
A actionBack() 0 10 2
A deferredRedirect() 0 7 3
A actionToggle() 0 7 2
A actionCheck() 0 24 4
A goBack() 0 9 2
A actionApiEnable() 0 19 3
A actionApiDisable() 0 18 3
A actionApiTemporarySecret() 0 10 1
1
<?php
2
/**
3
 * Multi-factor authentication for Yii2 projects
4
 *
5
 * @link      https://github.com/hiqdev/yii2-mfa
6
 * @package   yii2-mfa
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2016-2018, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\yii2\mfa\controllers;
12
13
use hiqdev\yii2\mfa\base\ApiMfaIdentityInterface;
14
use hiqdev\yii2\mfa\base\MfaIdentityInterface;
15
use hiqdev\yii2\mfa\behaviors\OauthLoginBehavior;
16
use hiqdev\yii2\mfa\forms\InputForm;
17
use hiqdev\yii2\mfa\validator\BackUrlValidatorInterface;
18
use Yii;
19
use yii\filters\AccessControl;
20
use yii\filters\ContentNegotiator;
21
use yii\filters\VerbFilter;
22
use yii\web\Response;
23
24
/**
25
 * TOTP controller.
26
 * Time-based One Time Password.
27
 */
28
class TotpController extends \yii\web\Controller
29
{
30
    private const TOTP_BACK_URL = 'totp-back-url';
31
32
    public $enableCsrfValidation = false;
33
34
    public function behaviors()
35
    {
36
        return array_merge(parent::behaviors(), [
37
            'filterApi' => [
38
                'class' => OauthLoginBehavior::class,
39
                'only' => ['api-temporary-secret', 'api-disable', 'api-enable'],
40
            ],
41
            'access' => [
42
                'class' => AccessControl::class,
43
                'denyCallback' => [$this, 'denyCallback'],
44
                'rules' => [
45
                    // ? - guest
46
                    [
47
                        'actions' => ['check'],
48
                        'roles' => ['?'],
49
                        'allow' => true,
50
                    ],
51
                    // @ - authenticated
52
                    [
53
                        'actions' => ['enable', 'disable', 'toggle', 'api-temporary-secret', 'api-disable', 'api-enable', 'back'],
54
                        'roles' => ['@'],
55
                        'allow' => true,
56
                    ],
57
                ],
58
            ],
59
            'verbFilter' => [
60
                'class' => VerbFilter::class,
61
                'actions' => [
62
                    'api-temporary-secret' => ['POST'],
63
                    'api-enable' => ['POST'],
64
                    'api-disable' => ['POST'],
65
                ],
66
            ],
67
            'contentNegotiator' => [
68
                'class' => ContentNegotiator::class,
69
                'only' => ['api-temporary-secret', 'api-disable', 'api-enable'],
70
                'formats' => [
71
                    'application/json' => Response::FORMAT_JSON,
72
                ],
73
            ],
74
        ]);
75
    }
76
77
    public function denyCallback()
78
    {
79
        return $this->goHome();
80
    }
81
82
    public function actionEnable($back = null)
83
    {
84
        /** @var MfaIdentityInterface $user */
85
        $user = Yii::$app->user->identity;
86
        if ($user->getTotpSecret()) {
87
            Yii::$app->session->setFlash('error', Yii::t('mfa', 'Two-factor authentication is already enabled. Disable first.'));
88
89
            return empty($back) ? $this->goHome() : $this->deferredRedirect($back);
90
        }
91
92
        $model = new InputForm();
93
        $secret = $this->module->getTotp()->getSecret();
94
95
        if ($model->load(Yii::$app->request->post()) && $model->validate()) {
96
            if ($this->module->getTotp()->verifyCode($secret, $model->code)) {
97
                $user->setTotpSecret($secret);
98
                $this->module->getTotp()->setIsVerified(true);
99
                if ($user->save() && Yii::$app->user->login($user)) {
100
                    Yii::$app->session->setFlash('success', Yii::t('mfa', 'Two-factor authentication successfully enabled.'));
101
102
                    return empty($back) ? $this->goBack() : $this->deferredRedirect($back);
103
                } else {
104
                    Yii::$app->session->setFlash('error', Yii::t('mfa', 'Sorry, we have failed to enable two-factor authentication.'));
105
106
                    return empty($back) ? $this->goHome() : $this->deferredRedirect($back);
107
                }
108
            } else {
109
                $model->addError('code', Yii::t('mfa', 'Wrong verification code. Please verify your secret and try again.'));
110
            }
111
        }
112
113
        $qrcode = $this->module->getTotp()->getQRCodeImageAsDataUri($user->getUsername(), $secret);
114
115
        return $this->render('enable', compact('model', 'secret', 'qrcode'));
116
    }
117
118
    public function actionDisable($back = null)
119
    {
120
        /** @var MfaIdentityInterface $user */
121
        $user = Yii::$app->user->identity;
122
        $model = new InputForm();
123
        $secret = $user->getTotpSecret();
124
125
        if ($model->load(Yii::$app->request->post()) && $model->validate()) {
126
            if ($this->module->getTotp()->verifyCode($secret, $model->code)) {
127
                $this->module->getTotp()->removeSecret();
128
                $user->setTotpSecret('');
129
                if ($user->save()) {
130
                    Yii::$app->session->setFlash('success', Yii::t('mfa', 'Two-factor authentication successfully disabled.'));
131
                }
132
133
                return empty($back) ? $this->goBack() : $this->deferredRedirect($back);
134
            } else {
135
                $model->addError('code', Yii::t('mfa', 'Wrong verification code. Please verify your secret and try again.'));
136
            }
137
        }
138
        return $this->render('disable', compact('model'));
139
    }
140
141
    public function actionBack()
142
    {
143
        $url = Yii::$app->getSession()->get(self::TOTP_BACK_URL);
144
        if (empty($url)) {
145
            return $this->goBack();
146
        }
147
        Yii::$app->getSession()->remove(self::TOTP_BACK_URL);
148
149
        return $this->redirect($url);
150
    }
151
152
    public function deferredRedirect($url = null)
153
    {
154
        if (!empty($url) && Yii::createObject(BackUrlValidatorInterface::class)->validate($url)) {
155
            Yii::$app->getSession()->set(self::TOTP_BACK_URL, $url);
156
        }
157
        return $this->render('redirect');
158
    }
159
160
    public function actionToggle($back = null)
161
    {
162
        /** @var MfaIdentityInterface $user */
163
        $user = Yii::$app->user->identity;
164
165
        return empty($user->getTotpSecret()) ? $this->actionEnable($back) : $this->actionDisable($back);
166
    }
167
168
    public function actionCheck()
169
    {
170
        /** @var MfaIdentityInterface $user */
171
        $user = $this->module->getHalfUser();
172
        $model = new InputForm();
173
        $secret = $user->getTotpSecret();
174
175
        if ($model->load(Yii::$app->request->post()) && $model->validate()) {
176
            if ($this->module->getTotp()->verifyCode($secret, $model->code)) {
177
                $this->module->getTotp()->setIsVerified(true);
178
                Yii::$app->user->login($user);
179
180
                return $this->goBack();
181
            } else {
182
                $model->addError('code', Yii::t('mfa', 'Wrong verification code. Please verify your secret and try again.'));
183
            }
184
        }
185
186
        return $this->render('check', [
187
            'model' => $model,
188
            'issuer' => $this->module->getTotp()->issuer,
189
            'username' => $user->getUsername(),
190
        ]);
191
    }
192
193
    /**
194
     * @inheritDoc
195
     */
196
    public function goBack($defaultUrl = null)
197
    {
198
        $redirectUrl = Yii::$app->params['totpRedirectBackAction.url'];
199
        if (!empty($redirectUrl)) {
200
            return $this->redirect($redirectUrl);
201
        }
202
203
        return parent::goBack($defaultUrl);
204
    }
205
206
    public function actionApiEnable()
207
    {
208
        /** @var ApiMfaIdentityInterface $identity */
209
        $identity = \Yii::$app->user->identity;
210
        $secret = $identity->getTotpSecret();
211
        if (!empty($secret)) {
212
            return ['_error' => 'mfa already enabled' . $secret];
213
        }
214
215
        if (!$this->module->getTotp()->verifyCode($identity->getTemporarySecret(), $this->request->post('code', ''))) {
216
            return ['_error' => 'invalid totp code'];
217
        }
218
219
        $identity->setTotpSecret($identity->getTemporarySecret());
220
        $identity->setTemporarySecret(null);
221
        $identity->save();
222
223
        return ['id' => $identity->getId()];
0 ignored issues
show
Bug introduced by
The method getId() does not seem to exist on object<hiqdev\yii2\mfa\b...piMfaIdentityInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
224
    }
225
226
    public function actionApiDisable()
227
    {
228
        /** @var ApiMfaIdentityInterface $identity */
229
        $identity = \Yii::$app->user->identity;
230
        $secret = $identity->getTotpSecret();
231
        if (empty($secret)) {
232
            return ['_error' => 'mfa disabled, enable first'];
233
        }
234
235
        if (!$this->module->getTotp()->verifyCode($secret, $this->request->post('code', ''))) {
236
            return ['_error' => 'invalid totp code'];
237
        }
238
239
        $identity->setTotpSecret('');
240
        $identity->save();
241
242
        return ['id' => $identity->getId()];
0 ignored issues
show
Bug introduced by
The method getId() does not seem to exist on object<hiqdev\yii2\mfa\b...piMfaIdentityInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
243
    }
244
245
    public function actionApiTemporarySecret()
246
    {
247
        /** @var ApiMfaIdentityInterface $identity */
248
        $identity = \Yii::$app->user->identity;
249
        $secret = $this->module->getTotp()->getSecret();
250
        $identity->setTemporarySecret($secret);
251
        $identity->save();
252
253
        return ['secret' => $secret];
254
    }
255
}
256