Completed
Pull Request — master (#13)
by
unknown
24:06 queued 09:01
created

TotpController::goBack()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 0
cts 0
cp 0
rs 9.9666
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 6
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\MfaIdentityInterface;
14
use hiqdev\yii2\mfa\base\Recovery;
15
use hiqdev\yii2\mfa\base\RecoveryCodeCollection;
16
use hiqdev\yii2\mfa\forms\InputForm;
17
use Yii;
18
use yii\filters\AccessControl;
19
20
/**
21
 * TOTP controller.
22
 * Time-based One Time Password.
23
 */
24
class TotpController extends \yii\web\Controller
25
{
26
    public function behaviors()
27
    {
28
        return array_merge(
29
            parent::behaviors(),
30
            [
31
                'access' => [
32
                    'class' => AccessControl::class,
33
                    'denyCallback' => [$this, 'denyCallback'],
34
                    'rules' => [
35
                        // ? - guest
36
                        [
37
                            'actions' => ['check'],
38
                            'roles' => ['?'],
39
                            'allow' => true,
40
                        ],
41
                        // @ - authenticated
42
                        [
43
                            'actions' => ['enable', 'disable', 'toggle'],
44
                            'roles' => ['@'],
45
                            'allow' => true,
46
                        ],
47
                    ],
48
                ],
49
            ]
50
        );
51
    }
52
53
    public function denyCallback()
54
    {
55
        return $this->goHome();
56
    }
57
58
    public function actionEnable($back = null)
59
    {
60
        /** @var MfaIdentityInterface $user */
61
        $user = Yii::$app->user->identity;
62 View Code Duplication
        if ($user->getTotpSecret()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
63
            Yii::$app->session->setFlash(
64
                'error',
65
                Yii::t('mfa', 'Two-factor authentication is already enabled. Disable first.')
66
            );
67
68
            return empty($back) ? $this->goHome() : $this->deferredRedirect($back);
69
        }
70
71
        $model = new InputForm();
72
        $secret = $this->module->getTotp()->getSecret();
73
74
        if ($model->load(Yii::$app->request->post()) && $model->validate()) {
75
            if ($this->module->getTotp()->verifyCode($secret, $model->code)) {
76
                $user->setTotpSecret($secret);
77
                $this->module->getTotp()->setIsVerified(true);
78
                if ($user->save() && Yii::$app->user->login($user)) {
79
                    $recovery = new RecoveryCodeCollection();
80
                    $codes = $recovery->generate();
81
                    if (!$codes->save()) {
82
                        Yii::$app->session->setFlash(
83
                            'error',
84
                            Yii::t(
85
                                'mfa',
86
                                'Sorry, we have failed to generate your recovery codes. Please try again later.'
87
                            )
88
                        );
89
                    } else {
90
                        return $this->actionCodes($codes->getCodes(), $back);
91
                    }
92
                    Yii::$app->session->setFlash(
93
                        'success',
94
                        Yii::t('mfa', 'Two-factor authentication successfully enabled.')
95
                    );
96
97
                    return empty($back) ? $this->goBack() : $this->deferredRedirect($back);
98 View Code Duplication
                } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
99
                    Yii::$app->session->setFlash(
100
                        'error',
101
                        Yii::t(
102
                            'mfa',
103
                            'Sorry, we have failed to enable two-factor authentication.'
104
                        )
105
                    );
106
107
                    return empty($back) ? $this->goHome() : $this->deferredRedirect($back);
108
                }
109
            } else {
110
                $model->addError(
111
                    'code',
112
                    Yii::t('mfa', 'Wrong verification code. Please verify your secret and try again.')
113
                );
114
            }
115
        }
116
117
        $qrcode = $this->module->getTotp()->getQRCodeImageAsDataUri($user->getUsername(), $secret);
118
119
        return $this->render('enable', compact('model', 'secret', 'qrcode'));
120
    }
121
122
    public function actionCodes(array $codes, $back = null)
123
    {
124 View Code Duplication
        if (Yii::$app->request->post('mfa-codes-saved')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
125
            Yii::$app->session->setFlash(
126
                'success',
127
                Yii::t('mfa', 'Two-factor authentication successfully enabled.')
128
            );
129
130
            return empty($back) ? $this->goBack() : $this->deferredRedirect($back);
131
        }
132
133
        return $this->render('codes', compact('codes'));
134
    }
135
136
    public function actionDisable($back = null)
137
    {
138
        /** @var MfaIdentityInterface $user */
139
        $user = Yii::$app->user->identity;
140
        $model = new InputForm();
141
        $secret = $user->getTotpSecret();
142
143
        if ($model->load(Yii::$app->request->post()) && $model->validate()) {
144
            if ($this->module->getTotp()->verifyCode($secret, $model->code)) {
145
                $this->module->getTotp()->removeSecret();
146
                $user->setTotpSecret('');
147
                if ($user->save()) {
148
                    Yii::$app->session->setFlash(
149
                        'success',
150
                        Yii::t('mfa', 'Two-factor authentication successfully disabled.')
151
                    );
152
                }
153
154
                return empty($back) ? $this->goBack() : $this->deferredRedirect($back);
155
            } else {
156
                $model->addError(
157
                    'code',
158
                    Yii::t('mfa', 'Wrong verification code. Please verify your secret and try again.')
159
                );
160
            }
161
        }
162
163
        return $this->render('disable', compact('model'));
164
    }
165
166
    public function deferredRedirect($url = null)
167
    {
168
        return $this->render('redirect', compact('url'));
169
    }
170
171
    public function actionToggle($back = null)
172
    {
173
        /** @var MfaIdentityInterface $user */
174
        $user = Yii::$app->user->identity;
175
176
        return empty($user->getTotpSecret()) ? $this->actionEnable($back) : $this->actionDisable($back);
177
    }
178
179
    public function actionCheck(bool $useRecoveryCode = false)
180
    {
181
        if ($useRecoveryCode){
182
            return $this->actionRecover();
183
        }
184
        /** @var MfaIdentityInterface $user */
185
        $user = $this->module->getHalfUser();
186
        $model = new InputForm();
187
        $secret = $user->getTotpSecret();
188
189 View Code Duplication
        if ($model->load(Yii::$app->request->post()) && $model->validate()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
190
            if ($this->module->getTotp()->verifyCode($secret, $model->code)) {
191
                $this->module->getTotp()->setIsVerified(true);
192
                Yii::$app->user->login($user);
193
194
                return $this->goBack();
195
            } else {
196
                $model->addError(
197
                    'code',
198
                    Yii::t('mfa', 'Wrong verification code. Please verify your secret and try again.')
199
                );
200
            }
201
        }
202
203
        return $this->render(
204
            'check',
205
            [
206
                'model' => $model,
207
                'issuer' => $this->module->getTotp()->issuer,
208
                'username' => $user->getUsername(),
209
            ]
210
        );
211
    }
212
213
214
    public function actionRecover($back = null)
215
    {
216
        /** @var MfaIdentityInterface $user */
217
        $user = $this->module->getHalfUser();
218
        $model = new Recovery();
219
        $model->setUser($user->getId());
220
221 View Code Duplication
        if ($model->load(Yii::$app->request->post()) && $model->validate() && $model->verifyCode()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
222
            $this->module->getTotp()->setIsVerified(true);
223
            Yii::$app->user->login($user);
224
225
            return $this->goBack();
226
        } else {
227
            $model->addError(
228
                'code',
229
                Yii::t('mfa', 'Wrong recovery code. Please try again.')
230
            );
231
        }
232
233
        return $this->render(
234
            'recover',
235
            [
236
                'model' => $model,
237
                'issuer' => $this->module->getTotp()->issuer,
238
                'username' => $user->getUsername(),
239
            ]
240
        );
241
    }
242
243
    /**
244
     * @inheritDoc
245
     */
246
    public function goBack($defaultUrl = null)
247
    {
248
        $redirectUrl = Yii::$app->params['totpRedirectBackAction.url'];
249
        if (!empty($redirectUrl)) {
250
            return $this->redirect($redirectUrl);
251
        }
252
253
        return parent::goBack($defaultUrl);
254
    }
255
}
256