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) { |
|
|
|
|
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 |
|
|
|
|
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 |
|
|
|
|
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 |
|
|
|
|
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); |
|
|
|
|
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 = ''; |
|
|
|
|
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); |
|
|
|
|
747
|
|
|
} |
748
|
|
|
|
749
|
|
|
return $this->gatewaySdk; |
750
|
|
|
} |
751
|
|
|
} |
752
|
|
|
|
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.