Completed
Push — master ( 239745...33a760 )
by Andrii
14:37
created

TotpController::actionBack()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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