Completed
Push — master ( 89c200...239745 )
by Dmitry
30:59 queued 16:14
created

TotpController::actionApiTemporarySecret()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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