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() |
|
|
|
|
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) { |
|
|
|
|
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') { |
|
|
|
|
99
|
|
|
$providerData['validationTime'] = $options->threema_gateway_tfa_reversed_validation_setup * 60; //default: 10 minutes |
|
|
|
|
100
|
|
|
} else { |
101
|
|
|
$providerData['validationTime'] = $options->threema_gateway_tfa_reversed_validation * 60; //default: 3 minutes |
|
|
|
|
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) { |
|
|
|
|
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
|
|
|
|
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.