Completed
Pull Request — master (#16)
by
unknown
12:36
created

TotpController::actionApiTemporarySecret()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 11
c 0
b 0
f 0
ccs 0
cts 0
cp 0
rs 9.9
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\MfaIdentityInterface;
14
use hiqdev\yii2\mfa\behaviors\OauthLoginBehavior;
15
use hiqdev\yii2\mfa\forms\InputForm;
16
use Yii;
17
use yii\filters\AccessControl;
18
use yii\filters\ContentNegotiator;
19
use yii\filters\VerbFilter;
20
use yii\web\Response;
21
22
/**
23
 * TOTP controller.
24
 * Time-based One Time Password.
25
 */
26
class TotpController extends \yii\web\Controller
27
{
28
    public $enableCsrfValidation = false;
29
30
    public function behaviors()
31
    {
32
        return array_merge(parent::behaviors(), [
33
            'access' => [
34
                'class' => AccessControl::class,
35
                'denyCallback' => [$this, 'denyCallback'],
36
                'rules' => [
37
                    // ? - guest
38
                    [
39
                        'actions' => ['check'],
40
                        'roles' => ['?'],
41
                        'allow' => true,
42
                    ],
43
                    // @ - authenticated
44
                    [
45
                        'actions' => ['enable', 'disable', 'toggle'],
46
                        'roles' => ['@'],
47
                        'allow' => true,
48
                    ],
49
                    [
50
                        'actions' => ['api-temporary-secret', 'api-disable', 'api-enable'],
51
                        'allow' => true,
52
                    ]
53
                ],
54
            ],
55
            'verbFilter' => [
56
                'class' => VerbFilter::class,
57
                'actions' => [
58
                    'api-temporary-secret' => ['POST'],
59
                    'api-enable' => ['POST'],
60
                    'api-disable' => ['POST']
61
                ],
62
            ],
63
            'filterApi' => [
64
                'class' => OauthLoginBehavior::class,
65
                'only' => ['api-temporary-secret', 'api-disable', 'api-enable'],
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 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...
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 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...
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 deferredRedirect($url = null)
142
    {
143
        return $this->render('redirect', compact('url'));
144
    }
145
146
    public function actionToggle($back = null)
147
    {
148
        /** @var MfaIdentityInterface $user */
149
        $user = Yii::$app->user->identity;
150
151
        return empty($user->getTotpSecret()) ? $this->actionEnable($back) : $this->actionDisable($back);
152
    }
153
154
    public function actionCheck()
155
    {
156
        /** @var MfaIdentityInterface $user */
157
        $user = $this->module->getHalfUser();
158
        $model = new InputForm();
159
        $secret = $user->getTotpSecret();
160
161
        if ($model->load(Yii::$app->request->post()) && $model->validate()) {
162
            if ($this->module->getTotp()->verifyCode($secret, $model->code)) {
163
                $this->module->getTotp()->setIsVerified(true);
164
                Yii::$app->user->login($user);
165
166
                return $this->goBack();
167
            } else {
168
                $model->addError('code', Yii::t('mfa', 'Wrong verification code. Please verify your secret and try again.'));
169
            }
170
        }
171
172
        return $this->render('check', [
173
            'model' => $model,
174
            'issuer' => $this->module->getTotp()->issuer,
175
            'username' => $user->getUsername(),
176
        ]);
177
    }
178
179
    /**
180
     * @inheritDoc
181
     */
182
    public function goBack($defaultUrl = null)
183
    {
184
        $redirectUrl = Yii::$app->params['totpRedirectBackAction.url'];
185
        if (!empty($redirectUrl)) {
186
            return $this->redirect($redirectUrl);
187
        }
188
189
        return parent::goBack($defaultUrl);
190
    }
191
192
    public function actionApiEnable()
193
    {
194
        /** @var MfaIdentityInterface $identity */
195
        $identity = \Yii::$app->user->identity;
196
        $secret = $identity->getTotpSecret();
197
        if (!empty($secret)) {
198
            return ['_error' => 'mfa already enabled' . $secret];
199
        }
200
201 View Code Duplication
        if (!$this->module->getTotp()->verifyCode($identity->getTemporarySecret(), \Yii::$app->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...
202
            return ['_error' => 'invalid totp code'];
203
        }
204
205
        $identity->setTotpSecret($identity->getTemporarySecret());
206
        $identity->setTemporarySecret(null);
207
        $identity->save();
208
209
        return ['id' => $identity->getId()];
210
    }
211
212
    public function actionApiDisable()
213
    {
214
        /** @var MfaIdentityInterface $identity */
215
        $identity = \Yii::$app->user->identity;
216
        $secret = $identity->getTotpSecret();
217
        if (empty($secret)) {
218
            return ['_error' => 'mfa disabled, enable first'];
219
        }
220
221 View Code Duplication
        if (!$this->module->getTotp()->verifyCode($secret, \Yii::$app->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...
222
            return ['_error' => 'wrong code'];
223
        }
224
225
        $identity->setTotpSecret('');
226
        $identity->save();
227
228
        return ['id' => $identity->getId()];
229
    }
230
231
    public function actionApiTemporarySecret()
232
    {
233
        /** @var MfaIdentityInterface $identity */
234
        $identity = \Yii::$app->user->identity;
235
        $secret = $this->module->getTotp()->getSecret();
236
        $identity->setTemporarySecret($secret);
237
        /** TODO: think about lib usage */
238
        $identity->save();
239
240
        return ['secret' => $secret];
241
    }
242
}
243