Completed
Pull Request — master (#13)
by
unknown
14:55
created

TotpController::actionCodes()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13

Duplication

Lines 8
Ratio 61.54 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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