ThreemaGateway_Tfa_Reversed::verifyFromInput()   B
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 42
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 42
rs 8.439
c 0
b 0
f 0
cc 6
eloc 18
nc 6
nop 4
1
<?php
2
/**
3
 * Two factor authentication provider for Threema Gateway which waites for a
4
 * secret/code transfered via Threema.
5
 *
6
 * @package ThreemaGateway
7
 * @author rugk
8
 * @copyright Copyright (c) 2015-2016 rugk
9
 * @license MIT
10
 */
11
12
/**
13
 * TFA where the user sends a login secret via Threema.
14
 */
15
class ThreemaGateway_Tfa_Reversed extends ThreemaGateway_Tfa_AbstractProvider
16
{
17
    /**
18
     * Return a description of the 2FA methode.
19
     */
20
    public function getDescription()
21
    {
22
        /** @var XenForo_Options $options */
23
        $options = XenForo_Application::getOptions();
24
        /** @var array $params */
25
        $params = [
26
            'board' => $options->boardTitle
27
        ];
28
29
        return new XenForo_Phrase('tfa_' . $this->_providerId . '_desc', $params);
30
    }
31
32
    /**
33
     * Called when verifying displaying the choose 2FA mode.
34
     *
35
     * @return bool
36
     */
37 View Code Duplication
    public function canEnable()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
38
    {
39
        if (!parent::canEnable()) {
40
            return false;
41
        }
42
43
        // check whether it is activated in the settings
44
        /** @var XenForo_Options $options */
45
        $options = XenForo_Application::getOptions();
46
        if (!$options->threema_gateway_tfa_reversed) {
47
            return false;
48
        }
49
50
        // this 2FA mode requires end-to-end encryption
51
        if (!$this->gatewaySettings->isEndToEnd()) {
52
            return false;
53
        }
54
55
        // check specific permissions
56
        if (!$this->gatewayPermissions->hasPermission('receive') ||
57
            !$this->gatewayPermissions->hasPermission('fetch')
58
        ) {
59
            return false;
60
        }
61
62
        return true;
63
    }
64
65
    /**
66
     * Called when trying to verify user. Creates secret and registers callback
67
     * request.
68
     *
69
     * @param  string $context
70
     * @param  array  $user
71
     * @param  string $userIp
72
     * @param  array  $providerData
73
     * @return array
74
     */
75
    public function triggerVerification($context, array $user, $userIp, array &$providerData)
76
    {
77
        parent::triggerVerification($context, $user, $userIp, $providerData);
78
79
        if (!$providerData) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $providerData of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
80
            return [];
81
        }
82
83
        // this 2FA mode requires end-to-end encryption
84
        if (!$this->gatewaySettings->isEndToEnd()) {
85
            throw new XenForo_Exception(new XenForo_Phrase('threema_this_action_required_e2e'));
86
        }
87
88
        /** @var XenForo_Options $options */
89
        $options = XenForo_Application::getOptions();
90
91
        /** @var string $secret random 6 digit string */
92
        $secret = $this->generateRandomSecret();
93
94
        $providerData['secret']          = $secret;
95
        $providerData['secretGenerated'] = XenForo_Application::$time;
96
97
        //secret is only valid for some time
98 View Code Duplication
        if ($context == 'setup') {
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
            $providerData['validationTime'] = $options->threema_gateway_tfa_reversed_validation_setup * 60; //default: 10 minutes
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
100
        } else {
101
            $providerData['validationTime'] = $options->threema_gateway_tfa_reversed_validation * 60; //default: 3 minutes
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
102
        }
103
104
        // most importantly register message request for Threema callback
105
        $this->registerPendingConfirmationMessage(
106
            $providerData,
107
            ThreemaGateway_Model_TfaPendingMessagesConfirmation::PENDING_REQUEST_CODE,
108
            $user
109
        );
110
111
        return [];
112
    }
113
114
    /**
115
     * Called when trying to verify user. Shows code, so user can send it via
116
     * Threema.
117
     *
118
     * @param  XenForo_View $view
119
     * @param  string       $context
120
     * @param  array        $user
121
     * @param  array        $providerData
122
     * @param  array        $triggerData
123
     * @return string       HTML code
124
     */
125
    public function renderVerification(XenForo_View $view, $context, array $user,
126
                                        array $providerData, array $triggerData)
127
    {
128
        parent::renderVerification($view, $context, $user, $providerData, $triggerData);
129
130
        $triggerData['secret'] = $providerData['secret'];
131
        if ($providerData['useNumberSmilies']) {
132
            $triggerData['secretWithSmiley'] = ThreemaGateway_Helper_Emoji::parseUnicode(
133
                ThreemaGateway_Helper_Emoji::replaceDigits($triggerData['secret'])
134
            );
135
        }
136
137
        /** @var XenForo_Options $xenOptions */
138
        $xenOptions = XenForo_Application::getOptions();
139
140
        $params = [
141
            'data' => $providerData,
142
            'trigger' => $triggerData,
143
            'context' => $context,
144
            'validationTime' => $this->parseTime($providerData['validationTime']),
145
            'gatewayid' => $this->gatewaySettings->getId(),
146
            'autoTrigger' => $xenOptions->threema_gateway_tfa_reversed_auto_trigger
147
        ];
148
        return $view->createTemplateObject('two_step_threemagw_reversed', $params)->render();
149
    }
150
151
    /**
152
     * Called when trying to verify user. Checks whether the secret was received
153
     * from the Threema Gateway callback.
154
     *
155
     * @param string $context
156
     * @param array  $input
157
     * @param array  $user
158
     * @param array  $providerData
159
     *
160
     * @return bool
161
     */
162
    public function verifyFromInput($context, XenForo_Input $input, array $user, array &$providerData)
163
    {
164
        /** @var bool $result from parent, for error checking */
165
        $result = parent::verifyFromInput($context, $input, $user, $providerData);
166
167
        // let errors pass through
168
        if (!$result) {
169
            return $result;
170
        }
171
172
        // verify that secret has not expired yet
173
        if (!$this->verifySecretIsInTime($providerData)) {
174
            return false;
175
        }
176
177
        // check whether secret has been received at all
178
        if (!isset($providerData['receivedSecret'])) {
179
            return false;
180
        }
181
182
        // prevent replay attacks
183
        if (!$this->verifyNoReplayAttack($providerData, $providerData['receivedSecret'])) {
184
            return false;
185
        }
186
187
        // check whether the secret is the same as required
188
        if (!$this->stringCompare($providerData['secret'], $providerData['receivedSecret'])) {
189
            return false;
190
        }
191
192
        $this->updateReplayCheckData($providerData, $providerData['receivedSecret']);
193
194
        // unregister confirmation
195
        $this->unregisterPendingConfirmationMessage(
196
            $providerData,
197
            ThreemaGateway_Model_TfaPendingMessagesConfirmation::PENDING_REQUEST_CODE
198
        );
199
200
        $this->resetProviderOptionsForTrigger($context, $providerData);
201
202
        return true;
203
    }
204
205
    /**
206
     * Verifies the Treema ID formally after it was entered/changed.
207
     *
208
     * @param XenForo_Input $input
209
     * @param array         $user
210
     * @param array         $error
211
     *
212
     * @return array
213
     */
214
    public function verifySetupFromInput(XenForo_Input $input, array $user, &$error)
215
    {
216
        /** @var array $providerData */
217
        $providerData = parent::verifySetupFromInput($input, $user, $error);
218
219
        // let errors pass through
220
        if (!$providerData) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $providerData of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
221
            return $providerData;
222
        }
223
224
        //add other options to provider data
225
        $providerData['useNumberSmilies'] = $input->filterSingle('useNumberSmilies', XenForo_Input::BOOLEAN);
226
227
        return $providerData;
228
    }
229
230
    /**
231
     * Called before the setup verification is shown.
232
     *
233
     * @param array $providerData
234
     * @param array $triggerData
235
     *
236
     * @return bool
237
     */
238
    protected function initiateSetupData(array &$providerData, array &$triggerData)
239
    {
240
        return true;
241
    }
242
243
    /**
244
     * Generates the default provider options at setup time before it is
245
     * displayed to the user.
246
     *
247
     * @return array
248
     */
249
    protected function generateDefaultData()
250
    {
251
        return [
252
            'useNumberSmilies' => true,
253
        ];
254
    }
255
256
    /**
257
     * Adjust the view params for managing the 2FA mode, e.g. add special
258
     * params needed by your template.
259
     *
260
     * @param array  $viewParams
261
     * @param string $context
262
     * @param array  $user
263
     *
264
     * @return array
265
     */
266
    protected function adjustViewParams(array $viewParams, $context, array $user)
267
    {
268
        /** @var XenForo_Options $xenOptions */
269
        $xenOptions = XenForo_Application::getOptions();
270
271
        $viewParams += [
272
            'https' => XenForo_Application::$secure,
273
            'showqrcode' => $xenOptions->threema_gateway_tfa_reversed_show_qr_code,
274
            'gatewayid' => $this->gatewaySettings->getId()
275
        ];
276
277
        return $viewParams;
278
    }
279
280
    /**
281
     * Resets the provider options to make sure the current 2FA verification
282
     * does not affect the next one.
283
     *
284
     * @param string $context
285
     * @param array  $providerData
286
     */
287
    protected function resetProviderOptionsForTrigger($context, array &$providerData)
288
    {
289
        parent::resetProviderOptionsForTrigger($context, $providerData);
290
291
        unset($providerData['receivedSecret']);
292
    }
293
}
294