ThreemaGateway_Tfa_AbstractProvider   C
last analyzed

Complexity

Total Complexity 68

Size/Duplication

Total Lines 738
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 9

Importance

Changes 0
Metric Value
wmc 68
lcom 2
cbo 9
dl 0
loc 738
rs 5
c 0
b 0
f 0

30 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A getTitle() 0 4 1
A getDescription() 0 4 1
A generateInitialData() 0 6 1
A triggerVerification() 0 10 2
A renderVerification() 0 5 1
A verifyFromInput() 0 7 1
A verifySetupFromInput() 0 21 2
A canManage() 0 4 1
A requiresSetup() 0 4 1
A renderSetup() 0 5 1
D handleManage() 0 146 10
A meetsRequirements() 0 4 1
A canEnable() 0 5 2
initiateSetupData() 0 1 ?
generateDefaultData() 0 1 ?
adjustViewParams() 0 1 ?
A saveProviderOptions() 0 6 1
A resetProviderOptionsForTrigger() 0 5 1
A sendMessage() 0 9 1
B generateRandomSecret() 0 30 4
C getDefaultThreemaId() 0 39 12
B registerPendingConfirmationMessage() 0 33 3
A unregisterPendingConfirmationMessage() 0 15 1
A verifySecretIsInTime() 0 12 4
B verifyNoReplayAttack() 0 11 5
A updateReplayCheckData() 0 10 1
B parseTime() 0 27 6
A stringCompare() 0 4 1
A getSdk() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like ThreemaGateway_Tfa_AbstractProvider often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ThreemaGateway_Tfa_AbstractProvider, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Two factor authentication abstract provider for Threema Gateway.
4
 *
5
 * @package ThreemaGateway
6
 * @author rugk
7
 * @copyright Copyright (c) 2015-2016 rugk
8
 * @license MIT
9
 */
10
11
/**
12
 * Threema Gateway for two step authentication (TFA/2FA).
13
 */
14
abstract class ThreemaGateway_Tfa_AbstractProvider extends XenForo_Tfa_AbstractProvider
15
{
16
    /**
17
     * Variable, which will be filled with object of the Gateway Permissions class.
18
     *
19
     * @var ThreemaGateway_Handler_Permissions
20
     */
21
    protected $gatewayPermissions;
22
23
    /**
24
     * Variable, which will be filled with object of Gateway Settings later.
25
     *
26
     * @var ThreemaGateway_Handler_Settings
27
     */
28
    protected $gatewaySettings;
29
30
    /**
31
     * Variable, which will be filled with object of Gateway Handler later.
32
     *
33
     * It is private as {@link getSdk()} should be used. This makes sure the SDK
34
     * is only initialized when it is really needed.
35
     *
36
     * @var ThreemaGateway_Handler_PhpSdk
37
     */
38
    private $gatewaySdk = null;
39
40
    /**
41
     * Variable, which will be filled with object of Gateway Handler for server actions later.
42
     *
43
     * @var ThreemaGateway_Handler_Action_gatewayServer
44
     */
45
    protected $gatewayServer;
46
47
    /**
48
     * Variable, which will be filled with object of Gateway Handler for sending actions later.
49
     *
50
     * @var ThreemaGateway_Handler_Action_Sender
51
     */
52
    protected $gatewaySender;
53
54
    /**
55
     * Create provider.
56
     *
57
     * @param string $providerId Provider id
58
     */
59
    public function __construct($providerId)
60
    {
61
        parent::__construct($providerId);
62
        $this->gatewayPermissions = ThreemaGateway_Handler_Permissions::getInstance();
63
        $this->gatewaySettings    = new ThreemaGateway_Handler_Settings;
64
        $this->gatewayServer      = new ThreemaGateway_Handler_Action_GatewayServer;
65
        $this->gatewaySender      = new ThreemaGateway_Handler_Action_Sender;
66
    }
67
68
    /**
69
     * Return the title of the 2FA methode.
70
     */
71
    public function getTitle()
72
    {
73
        return new XenForo_Phrase('tfa_' . $this->_providerId);
74
    }
75
76
    /**
77
     * Return a description of the 2FA methode.
78
     */
79
    public function getDescription()
80
    {
81
        return new XenForo_Phrase('tfa_' . $this->_providerId . '_desc');
82
    }
83
84
    /**
85
     * Called when activated. Returns inital data of 2FA method.
86
     *
87
     * @param  array $user
88
     * @param  array $setupData
89
     * @return array
90
     */
91
    public function generateInitialData(array $user, array $setupData)
92
    {
93
        $this->gatewayPermissions->setUserId($user);
94
95
        return $setupData;
96
    }
97
98
    /**
99
     * Called when trying to verify user. Sends Threema message.
100
     *
101
     * @param  string $context
102
     * @param  array  $user
103
     * @param  string $userIp
104
     * @param  array  $providerData
105
     * @return array
106
     */
107
    public function triggerVerification($context, array $user, $userIp, array &$providerData)
108
    {
109
        $this->gatewayPermissions->setUserId($user);
110
111
        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...
112
            throw new XenForo_Exception(new XenForo_Phrase('threemagw_this_tfa_mode_is_not_setup'));
113
        }
114
115
        return [];
116
    }
117
118
    /**
119
     * Called when trying to verify user. Shows input for secret and such things.
120
     *
121
     * @param  XenForo_View $view
122
     * @param  string       $context
123
     * @param  array        $user
124
     * @param  array        $providerData
125
     * @param  array        $triggerData
126
     * @return string       HTML code
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
127
     */
128
    public function renderVerification(XenForo_View $view, $context, array $user,
129
                                        array $providerData, array $triggerData)
130
    {
131
        $this->gatewayPermissions->setUserId($user);
132
    }
133
134
    /**
135
     * Called when trying to verify user. Checks whether a given secret is valid.
136
     *
137
     * @param string $context
138
     * @param array  $input
139
     * @param array  $user
140
     * @param array  $providerData
141
     *
142
     * @return bool
143
     */
144
    public function verifyFromInput($context, XenForo_Input $input, array $user, array &$providerData)
145
    {
146
        $this->gatewayPermissions->setUserId($user);
147
148
        // if we returned nothing, the child methods would fail if they properly check the result
149
        return true;
150
    }
151
152
    /**
153
     * Verifies the Treema ID formally after it was entered/changed.
154
     *
155
     * @param XenForo_Input $input
156
     * @param array         $user
157
     * @param array         $error
158
     *
159
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
160
     */
161
    public function verifySetupFromInput(XenForo_Input $input, array $user, &$error)
162
    {
163
        $this->gatewayPermissions->setUserId($user);
164
165
        /** @var array $providerData */
166
        $providerData = [];
167
        /** @var string $threemaid Threema ID given as parameter */
168
        $threemaid = $input->filterSingle('threemaid', XenForo_Input::STRING);
169
170
        //check Threema ID
171
        /** @var string $verifyError */
172
        $verifyError = '';
173
        if (!ThreemaGateway_Handler_Validation::checkThreemaId($threemaid, 'personal', $verifyError)) {
174
            // incorrect Threema ID
175
            $error[] = $verifyError;
176
            return [];
177
        }
178
179
        // Threema ID verification succeeded
180
        $providerData['threemaid'] = $threemaid;
181
    }
182
183
    /**
184
     * @return bool
185
     */
186
    public function canManage()
187
    {
188
        return true;
189
    }
190
191
192
    /**
193
     * States whether the setup is required.
194
     *
195
     * @return bool
196
     */
197
    public function requiresSetup()
198
    {
199
        return true;
200
    }
201
202
    /**
203
     * Called when setting up the provider before the setup page is shown.
204
     *
205
     * Currently this is not correctly implemented in XenForo.
206
     * See {@link https://xenforo.com/community/threads/1-5-documentation-for-two-step-authentication.102846/#post-1031047}
207
     *
208
     * @param XenForo_View $view
209
     * @param array        $user
210
     *
211
     * @return string HTML code
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
212
     */
213
    public function renderSetup(XenForo_View $view, array $user)
214
    {
215
        // redirected by ThreemaGateway_ControllerPublic_Account->actionTwoStepEnable
216
        // to handleManage.
217
    }
218
219
    /**
220
     * Handles settings of user.
221
     *
222
     * @param XenForo_Controller $controller
223
     * @param array              $user
224
     * @param array              $providerData
225
     *
226
     * @return null|ThreemaGateway_ViewPublic_TfaManage
227
     */
228
    final public function handleManage(XenForo_Controller $controller, array $user, array $providerData)
229
    {
230
        $this->gatewayPermissions->setUserId($user);
231
232
        /** @var XenForo_Input $input */
233
        $input   = $controller->getInput();
234
        /** @var Zend_Controller_Request_Http $request */
235
        $request = $controller->getRequest();
236
        /** @var XenForo_Session $session */
237
        $session = XenForo_Application::getSession();
238
239
        /** @var array|null $newProviderData */
240
        $newProviderData = null;
241
        /** @var array|null $newTriggerData */
242
        $newTriggerData  = null;
243
        /** @var bool $showSetup */
244
        $showSetup       = false;
245
        /** @var string $context */
246
        $context         = 'setup';
247
        /** @var string $threemaId */
248
        $threemaId       = '';
249
250
        /* Possible values of $context in order of usual appearance
251
        firstsetup      Input=Threema ID    User enables 2FA provider the first time.
252
        setupvalidation Input=2FA secret    Confirming 2FA in initial setup. (2FA input context: setup)
253
254
        setup           Input=Threema ID    UI to change settings of 2FA provider (shows when user clicks on "manage")
255
        update          Input=2FA secret    Confirming 2FA when settings changed. (2FA input context: setup)
256
257
        <not here>      Input=2FA c. only   Login page, where secret requested (2FA input context: login)
258
259
        The usual template is account_two_step_threemagw_conventional_manage, which includes
260
        account_two_step_threemagw_conventional every time when a 2FA secret is requested. If so
261
        this "subtemplate" always gets the context "setup".
262
        Only when logging in this template is included by itself and gets the context "login".
263
        */
264
265
        /* Ways this function can go: Input (filterSingle) --> action --> output ($context)
266
        Initial setup:
267
            no $providerData --> set default options & Threema ID --> firstsetup
268
            step = setup --> show page where user can enter 2FA secret --> setupvalidation
269
            <verification not done in method>
270
271
        Manage:
272
            ... (last else block) --> manage page: show setup --> setup
273
            manage --> show page where user can enter 2FA secret --> update
274
            confirm --> check 2FA secret & use settings if everything is right --> <null>
275
276
        Login:
277
            <not manmaged in this function>
278
        */
279
280
        if ($controller->isConfirmedPost()) {
281
            /** @var string $sessionKey the key for the temporary saved provider data. */
282
            $sessionKey = 'tfaData_' . $this->_providerId;
283
284
            //setup changed
285
            if ($input->filterSingle('manage', XenForo_Input::BOOLEAN)) {
286
                //provider data (settings) changed
287
288
                //read and verify options
289
                /** @var string $error */
290
                $error           = '';
291
                $newProviderData = $this->verifySetupFromInput($input, $user, $error);
292
                if (!$newProviderData) {
293
                    return $controller->responseError($error);
294
                }
295
296
                //check if there is a new ID, which would require revalidation
297
                if ($newProviderData['threemaid'] === $providerData['threemaid']) {
298
                    //the same Threema ID - use options instantly
299
                    $this->saveProviderOptions($user, $newProviderData);
300
                    return null;
301
                }
302
303
                //validation is required, revalidate this thing...
304
                $newTriggerData = $this->triggerVerification('setup', $user, $request->getClientIp(false), $newProviderData);
305
306
                $session->set($sessionKey, $newProviderData);
307
                $showSetup = true;
308
                $context   = 'update';
309
            } elseif ($input->filterSingle('confirm', XenForo_Input::BOOLEAN)) {
310
                //confirm setup validation
311
312
                //validate new provider data
313
                $newProviderData = $session->get($sessionKey);
314
                if (!is_array($newProviderData)) {
315
                    return null;
316
                }
317
318
                if (!$this->verifyFromInput('setup', $input, $user, $newProviderData)) {
319
                    return $controller->responseError(new XenForo_Phrase('two_step_verification_value_could_not_be_confirmed'));
320
                }
321
322
                //update provider as everything is okay
323
                $this->saveProviderOptions($user, $newProviderData);
324
                $session->remove($sessionKey);
325
326
                return null;
327
            } elseif ($input->filterSingle('step', XenForo_Input::BOOLEAN) == 'setup') {
328
                //show "real" setup (where you have to confirm validation)
329
                $context = 'setupvalidation';
330
331
                $newProviderData = $providerData;
332
                $session->set($sessionKey, $newProviderData);
333
334
                $newTriggerData = []; //is not used anyway...
335
                $showSetup      = true;
336
337
                $this->initiateSetupData($newProviderData, $newTriggerData);
338
            } else {
339
                throw new XenForo_Exception('Request invalid.');
340
            }
341
        } elseif (empty($providerData)) { //no previous settings
342
            //show first setup page (you can enter your Threema ID)
343
            $context = 'firstsetup';
344
345
            //set default values of options
346
            $providerData = $this->generateDefaultData();
347
348
            $threemaId = $this->getDefaultThreemaId($user);
349
        } else {
350
            //first manage page ($context = setup)
351
            $threemaId = $providerData['threemaid'];
352
        }
353
354
        /** @var array $viewParams parameters for XenForo_ControllerResponse_View */
355
        $viewParams = [
356
            'provider' => $this,
357
            'providerId' => $this->_providerId,
358
            'user' => $user,
359
            'providerData' => $providerData,
360
            'newProviderData' => $newProviderData,
361
            'newTriggerData' => $newTriggerData,
362
            'showSetup' => $showSetup,
363
            'context' => $context,
364
            'threemaId' => $threemaId
365
        ];
366
        $viewParams = $this->adjustViewParams($viewParams, $context, $user);
367
368
        return $controller->responseView(
369
            'ThreemaGateway_ViewPublic_TfaManage',
370
            'account_two_step_' . $this->_providerId . '_manage',
371
            $viewParams
372
        );
373
    }
374
375
    /**
376
     * Called when trying to verify user. Checks whether a user meets the
377
     * requirements.
378
     *
379
     * @param array  $user
380
     * @param object $error
381
     *
382
     * @return bool
383
     */
384
    public function meetsRequirements(array $user, &$error)
385
    {
386
        return true;
387
    }
388
389
    /**
390
     * Called when verifying displaying the choose 2FA mode.
391
     *
392
     * @return bool
393
     */
394
    public function canEnable()
395
    {
396
        // check necessary permissions
397
        return $this->gatewaySettings->isReady() && $this->gatewayPermissions->hasPermission('tfa');
398
    }
399
400
    /**
401
     * Called before the setup verification is shown.
402
     *
403
     * @param array $providerData
404
     * @param array $triggerData
405
     *
406
     * @return bool
407
     */
408
    abstract protected function initiateSetupData(array &$providerData, array &$triggerData);
409
410
    /**
411
     * Generates the default provider options at setup time before it is
412
     * displayed to the user.
413
     *
414
     * @return array
415
     */
416
    abstract protected function generateDefaultData();
417
418
    /**
419
     * Adjust the view params for managing the 2FA mode, e.g. add special
420
     * params needed by your template.
421
     *
422
     * @param array  $viewParams
423
     * @param string $context
424
     * @param array  $user
425
     *
426
     * @return array
427
     */
428
    abstract protected function adjustViewParams(array $viewParams, $context, array $user);
429
430
    /**
431
     * Saves new provider options to database.
432
     *
433
     * @param array $user
434
     * @param array $options
435
     */
436
    protected function saveProviderOptions($user, array $options)
437
    {
438
        /** @var XenForo_Model_Tfa $tfaModel */
439
        $tfaModel = XenForo_Model::create('XenForo_Model_Tfa');
440
        $tfaModel->enableUserTfaProvider($user['user_id'], $this->_providerId, $options);
441
    }
442
443
    /**
444
     * Resets the provider options to make sure the current 2FA verification
445
     * does not affect the next one.
446
     *
447
     * Please expand this if you have more values, which need to be reset, but
448
     * please do not forget to call the parent.
449
     *
450
     * @param string $context
451
     * @param array  $providerData
452
     */
453
    protected function resetProviderOptionsForTrigger($context, array &$providerData)
454
    {
455
        unset($providerData['secret']);
456
        unset($providerData['secretGenerated']);
457
    }
458
459
    /**
460
     * Sends a message to a user and chooses automatically whether E2E mode can
461
     * be used.
462
     *
463
     * @param array $receiverId The Threema ID to who
464
     * @param array $xenPhrase  The message as a phrase, which should be sent
465
     */
466
    final protected function sendMessage($receiverId, XenForo_Phrase $xenPhrase)
467
    {
468
        // parse message
469
        $messageText = $xenPhrase->render();
470
        $messageText = ThreemaGateway_Helper_Emoji::parseUnicode($messageText);
471
472
        // send message
473
        return $this->gatewaySender->sendAuto($receiverId, $messageText);
0 ignored issues
show
Documentation introduced by
$receiverId is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
474
    }
475
476
    /**
477
     * Generates a random numeric string consisting of digits.
478
     *
479
     * @param  int    $length The length of the string (default: 6)
480
     * @return string
481
     */
482
    final protected function generateRandomSecret($length = 6)
483
    {
484
        /** @var string $secret */
485
        $secret = '';
486
487
        try {
488
            //use own Sodium method
489
            $secret = ThreemaGateway_Helper_Random::getRandomNumeric($length);
490
        } catch (Exception $e) {
491
            // ignore errors
492
        }
493
494
        //use XenForo method as a fallback
495
        if (!$secret || !ctype_digit($secret)) {
496
            // ThreemaGateway_Helper_Random internally uses XenForo as a
497
            // fallback
498
            $random = ThreemaGateway_Helper_Random::getRandomBytes(4);
499
500
            // that's XenForo style
501
            $secret = (
502
                ((ord($random[0]) & 0x7f) << 24) |
503
                ((ord($random[1]) & 0xff) << 16) |
504
                ((ord($random[2]) & 0xff) << 8) |
505
                (ord($random[3]) & 0xff)
506
                    ) % pow(10, $length);
507
            $secret = str_pad($secret, $length, '0', STR_PAD_LEFT);
508
        }
509
510
        return $secret;
511
    }
512
513
    /**
514
     * Gets the default Threema ID using different sources.
515
     *
516
     * @param  array        $user
517
     * @return string|false
518
     */
519
    final protected function getDefaultThreemaId(array $user)
520
    {
521
        /** @var XenForo_Options $options */
522
        $options = XenForo_Application::getOptions();
523
        // check for custom user field
524
        if (array_key_exists('threemaid', $user['customFields']) &&
525
            $user['customFields']['threemaid'] != '') {
526
527
            //use custom user field
528
            return $user['customFields']['threemaid'];
529
        }
530
531
        // lookup mail address
532
        if ($options->threema_gateway_tfa_autolookupmail &&
533
            $user['user_state'] == 'valid') {
534
535
            //lookup mail
536
            try {
537
                return $this->gatewaySdkServer->lookupMail($user['email']);
538
            } catch (Exception $e) {
539
                //ignore failures, fall through/try next method
540
            }
541
        }
542
543
        // lookup phone number
544
        if ($options->threema_gateway_tfa_autolookupphone && //verify ACP permission
545
            $options->threema_gateway_tfa_autolookupphone['enabled'] &&
546
            $options->threema_gateway_tfa_autolookupphone['userfield'] && //verify ACP setup
547
            array_key_exists($options->threema_gateway_tfa_autolookupphone['userfield'], $user['customFields']) && //verify user field
548
            $user['customFields'][$options->threema_gateway_tfa_autolookupphone['userfield']] != '') {
549
550
            //lookup phone number
551
            try {
552
                return $this->gatewaySdkServer->lookupPhone($user['customFields'][$options->threema_gateway_tfa_autolookupphone['userfield']]);
553
            } catch (Exception $e) {
554
                //ignore failure
555
            }
556
        }
557
    }
558
559
    /**
560
     * Register a request for a new pending confirmation message.
561
     *
562
     * @param array      $providerData
563
     * @param int        $pendingType  What type of message request this is.
564
     *                                 You should use one of the PENDING_* constants
565
     *                                 in the Model (ThreemaGateway_Model_TfaPendingMessagesConfirmation).
566
     * @param array      $user
567
     * @param string|int $extraData    Any extra data you want to save in the database.
568
     *
569
     * @return bool
570
     */
571
    final protected function registerPendingConfirmationMessage(array $providerData, $pendingType, array $user, $extraData = null)
572
    {
573
        /** @var ThreemaGateway_Model_TfaPendingMessagesConfirmation $model */
574
        $model = XenForo_DataWriter::create('ThreemaGateway_Model_TfaPendingMessagesConfirmation');
575
        /** @var ThreemaGateway_DataWriter_TfaPendingMessagesConfirmation $dataWriter */
576
        $dataWriter = XenForo_DataWriter::create('ThreemaGateway_DataWriter_TfaPendingMessagesConfirmation');
577
578
579
        // check whether the same request is already issued, if so overwrite it
580
        if ($model->getPending($providerData['threemaid'], $this->_providerId, $pendingType)) {
581
            $dataWriter->setExistingData([
582
                ThreemaGateway_Model_TfaPendingMessagesConfirmation::DB_TABLE => [
583
                    'threema_id' => $providerData['threemaid'],
584
                    'provider_id' => $this->_providerId,
585
                    'pending_type' => $pendingType
586
                ]
587
            ]);
588
        }
589
590
        $dataWriter->set('threema_id', $providerData['threemaid']);
591
        $dataWriter->set('provider_id', $this->_providerId);
592
        $dataWriter->set('pending_type', $pendingType);
593
594
        $dataWriter->set('user_id', $user['user_id']);
595
        $dataWriter->set('session_id', XenForo_Application::getSession()->getSessionId());
596
597
        if ($extraData) {
598
            $dataWriter->set('extra_data', $extraData);
599
        }
600
        $dataWriter->set('expiry_date', $providerData['secretGenerated'] + $providerData['validationTime']);
601
602
        return $dataWriter->save();
603
    }
604
605
    /**
606
     * Register a request for a new pending confirmation message.
607
     *
608
     * @param array $providerData
609
     * @param int   $pendingType  What type of message request this is.
610
     *                            You should use one of the PENDING_* constants
611
     *                            in the Model (ThreemaGateway_Model_TfaPendingMessagesConfirmation).
612
     *
613
     * @return bool
614
     */
615
    final protected function unregisterPendingConfirmationMessage(array $providerData, $pendingType)
616
    {
617
        /** @var ThreemaGateway_DataWriter_TfaPendingMessagesConfirmation $dataWriter */
618
        $dataWriter = XenForo_DataWriter::create('ThreemaGateway_DataWriter_TfaPendingMessagesConfirmation');
619
620
        $dataWriter->setExistingData([
621
            ThreemaGateway_Model_TfaPendingMessagesConfirmation::DB_TABLE => [
622
                'threema_id' => $providerData['threemaid'],
623
                $this->_providerId,
624
                'pending_type' => $pendingType
625
            ]
626
        ]);
627
628
        return $dataWriter->delete();
629
    }
630
631
    /**
632
     * Verifies whether the new secret is valid considering timing information
633
     * about the current secret.
634
     *
635
     * @param  array $providerData
636
     * @return bool
637
     */
638
    final protected function verifySecretIsInTime(array $providerData)
639
    {
640
        if (empty($providerData['secret']) || empty($providerData['secretGenerated'])) {
641
            return false;
642
        }
643
644
        if ((XenForo_Application::$time - $providerData['secretGenerated']) > $providerData['validationTime']) {
645
            return false;
646
        }
647
648
        return true;
649
    }
650
651
    /**
652
     * Verifies whether the new secret is valid by comparing it with the previous
653
     * secret.
654
     *
655
     * @param  array  $providerData
656
     * @param  string $newSecret    the new secret, which is currently checked/verified
657
     * @return bool
658
     */
659
    final protected function verifyNoReplayAttack(array $providerData, $newSecret)
660
    {
661
        if (!empty($providerData['lastSecret']) && $this->stringCompare($providerData['lastSecret'], $newSecret)) {
662
            // prevent replay attacks: once the secret has been used, don't allow it to be used in the slice again
663
            if (!empty($providerData['lastSecretTime']) && (XenForo_Application::$time - $providerData['lastSecretTime']) < $providerData['validationTime']) {
664
                return false;
665
            }
666
        }
667
668
        return true;
669
    }
670
671
    /**
672
     * Updates the data used by {@see verifyNoReplayAttack()} to prevent replay attacks.
673
     *
674
     * @param  array      $providerData
675
     * @param  string|int $secret       The currently processed (& verified) secret
676
     * @return bool
677
     */
678
    final protected function updateReplayCheckData(array &$providerData, $secret)
679
    {
680
        // save current secret for later replay attack checks
681
        $providerData['lastSecret']     = $secret;
682
        $providerData['lastSecretTime'] = XenForo_Application::$time;
683
        unset($providerData['secret']);
684
        unset($providerData['secretGenerated']);
685
686
        return true;
687
    }
688
689
    /**
690
     * Parse a given number of seconds to a human-readble format.
691
     *
692
     * @param  int    $seconds
693
     * @return string
694
     */
695
    final protected function parseTime($seconds)
696
    {
697
        /** @var string $displayTime output/result */
698
        $displayTime = '';
0 ignored issues
show
Unused Code introduced by
$displayTime is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
699
        /** @var int $minutes */
700
        $minutes = floor($seconds / 60);
701
        /** @var int $hours */
702
        $hours = floor($minutes / 60);
703
704
        if ($minutes <= 1) {
705
            $displayTime = new XenForo_Phrase('threemagw_one_minute');
706
        } elseif ($minutes < 60) {
707
            $displayTime = $minutes . ' ' . new XenForo_Phrase('threemagw_minutes');
708
        // hours below (more than 60 minutes)
709
        } elseif ($minutes <= 61) {
710
            $displayTime = new XenForo_Phrase('threemagw_one_hour');
711
        } elseif ($hours <= 1) {
712
            $displayTime = $hours . ' ' . new XenForo_Phrase('threemagw_hours');
713
        // days below (more than 1 hour)
714
        } elseif ($hours <= 24) {
715
            $displayTime = new XenForo_Phrase('threemagw_one_day');
716
        } else {
717
            $displayTime = floor($hours / 24) . ' ' . new XenForo_Phrase('threemagw_days');
718
        }
719
720
        return (string) $displayTime;
721
    }
722
723
    /**
724
     * Checks whether a string is the same (returns true) or not (returns false).
725
     *
726
     * This should be used for security-sensitive things as it checks the
727
     * strings constant-time.
728
     *
729
     * @param  string $string1
730
     * @param  string $string2
731
     * @return bool
732
     */
733
    final protected function stringCompare($string1, $string2)
734
    {
735
        return $this->getSdk()->getCryptTool()->stringCompare($string1, $string2);
736
    }
737
738
    /**
739
     * Returns the PHP SDK object.
740
     *
741
     * @return ThreemaGateway_Handler_PhpSdk
742
     */
743
    final protected function getSdk()
744
    {
745
        if ($this->gatewaySdk === null) {
746
            $this->gatewaySdk = ThreemaGateway_Handler_PhpSdk::getInstance($this->gatewaySettings);
0 ignored issues
show
Documentation Bug introduced by
It seems like \ThreemaGateway_Handler_...$this->gatewaySettings) can also be of type object<Singleton>. However, the property $gatewaySdk is declared as type object<ThreemaGateway_Handler_PhpSdk>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
747
        }
748
749
        return $this->gatewaySdk;
750
    }
751
}
752