1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* ***************************************************************************** |
5
|
|
|
* Contributions to this work were made on behalf of the GÉANT project, a |
6
|
|
|
* project that has received funding from the European Union’s Framework |
7
|
|
|
* Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus), |
8
|
|
|
* Horizon 2020 research and innovation programme under Grant Agreements No. |
9
|
|
|
* 691567 (GN4-1) and No. 731122 (GN4-2). |
10
|
|
|
* On behalf of the aforementioned projects, GEANT Association is the sole owner |
11
|
|
|
* of the copyright in all material which was developed by a member of the GÉANT |
12
|
|
|
* project. GÉANT Vereniging (Association) is registered with the Chamber of |
13
|
|
|
* Commerce in Amsterdam with registration number 40535155 and operates in the |
14
|
|
|
* UK as a branch of GÉANT Vereniging. |
15
|
|
|
* |
16
|
|
|
* Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. |
17
|
|
|
* UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK |
18
|
|
|
* |
19
|
|
|
* License: see the web/copyright.inc.php file in the file structure or |
20
|
|
|
* <base_url>/copyright.php after deploying the software |
21
|
|
|
*/ |
22
|
|
|
/** |
23
|
|
|
* This file contains code for diagnostics tests |
24
|
|
|
* |
25
|
|
|
* @author Stefan Winter <[email protected]> |
26
|
|
|
* @author Maja Gorecka-Wolniewicz <[email protected]> |
27
|
|
|
* |
28
|
|
|
* @package Developer |
29
|
|
|
* |
30
|
|
|
*/ |
31
|
|
|
namespace core\diag; |
32
|
|
|
|
33
|
|
|
use \Exception; |
|
|
|
|
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* The overall coordination class that runs all kinds of tests to find out where |
37
|
|
|
* and what is wrong. Operates on the realm of a user. Can do more magic if it |
38
|
|
|
* also knows which federation the user is currently positioned in, or even |
39
|
|
|
* which exact hotspot to analyse. |
40
|
|
|
*/ |
41
|
|
|
class Telepath extends AbstractTest |
42
|
|
|
{ |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* the realm we are testing |
46
|
|
|
* |
47
|
|
|
* @var string |
48
|
|
|
*/ |
49
|
|
|
private $realm; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* the federation where the user currently is |
53
|
|
|
* |
54
|
|
|
* @var string|NULL |
55
|
|
|
*/ |
56
|
|
|
private $visitedFlr; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* the identifier of the hotspot where the user currently is |
60
|
|
|
* |
61
|
|
|
* @var string|NULL |
62
|
|
|
*/ |
63
|
|
|
private $visitedHotspot; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* the CAT profile to which the realm belongs, if any |
67
|
|
|
* |
68
|
|
|
* @var integer |
69
|
|
|
*/ |
70
|
|
|
private $catProfile; |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* the identifier of the associated IdP in the external DB, if any |
74
|
|
|
* |
75
|
|
|
* @var string |
76
|
|
|
*/ |
77
|
|
|
private $dbIdP; |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* the federation to which the realm belongs; can be NULL if we can't infer |
81
|
|
|
* from domain ending nor find it in the DB |
82
|
|
|
* |
83
|
|
|
* @var string|NULL |
84
|
|
|
*/ |
85
|
|
|
private $idPFederation; |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* instance of the RADIUSTests suite we use to meditate with |
89
|
|
|
* |
90
|
|
|
* @var \core\diag\RADIUSTests |
91
|
|
|
*/ |
92
|
|
|
private $testsuite; |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* prime the Telepath with info it needs to know to successfully meditate over the problem |
96
|
|
|
* @param string $realm the realm of the user |
97
|
|
|
* @param string|null $visitedFlr which NRO is the user visiting |
98
|
|
|
* @param string|null $visitedHotspot external DB ID of the hotspot he visited |
99
|
|
|
*/ |
100
|
|
|
public function __construct(string $realm, $visitedFlr = NULL, $visitedHotspot = NULL) |
101
|
|
|
{ |
102
|
|
|
// Telepath is the first one in a chain, no previous inputs allowed |
103
|
|
|
if (isset($_SESSION) && isset($_SESSION["SUSPECTS"])) { |
104
|
|
|
unset($_SESSION["SUSPECTS"]); |
105
|
|
|
} |
106
|
|
|
if (isset($_SESSION) && isset($_SESSION["EVIDENCE"])) { |
107
|
|
|
unset($_SESSION["EVIDENCE"]); |
108
|
|
|
} |
109
|
|
|
// now fill with default values |
110
|
|
|
parent::__construct(); |
111
|
|
|
$this->realm = $realm; |
112
|
|
|
$this->additionalFindings['REALM'] = $this->realm; |
113
|
|
|
$this->visitedFlr = $visitedFlr; |
114
|
|
|
$this->visitedHotspot = $visitedHotspot; |
115
|
|
|
$links = \core\Federation::determineIdPIdByRealm($realm); |
116
|
|
|
$this->catProfile = $links["CAT"]; |
117
|
|
|
$this->dbIdP = $links["EXTERNAL"]; |
118
|
|
|
$this->idPFederation = $links["FEDERATION"]; |
119
|
|
|
// this is NULL if the realm is not known in either DB |
120
|
|
|
// if so, let's try a regex to extract the ccTLD if any |
121
|
|
|
$matches = []; |
122
|
|
|
if ($this->idPFederation === NULL && preg_match("/\.(..)$/", $realm, $matches)) { |
123
|
|
|
$this->idPFederation = strtoupper($matches[1]); |
124
|
|
|
} |
125
|
|
|
$this->loggerInstance->debug(4, "XYZ: IdP-side NRO is " . $this->idPFederation . "\n"); |
126
|
|
|
} |
127
|
|
|
/* The eduroam OT monitoring has the following return codes: |
128
|
|
|
* |
129
|
|
|
|
130
|
|
|
Status codes |
131
|
|
|
|
132
|
|
|
0 - O.K. |
133
|
|
|
-1 - Accept O.K. Reject No |
134
|
|
|
-2 - Reject O.K. Accept No |
135
|
|
|
-3 - Accept No Reject No |
136
|
|
|
-9 - system error |
137
|
|
|
-10 - Accept O.K. Reject timeou |
138
|
|
|
-11 - Accept O.K. Reject no EAP |
139
|
|
|
-20 - Reject O.K. Accept timeou |
140
|
|
|
-21 - Reject O.K. Accept no EAP |
141
|
|
|
-31 - Accept No Reject timeou |
142
|
|
|
-32 - Accept Timeout Reject no |
143
|
|
|
-33 - Accept Timeout Reject timeou |
144
|
|
|
-35 - Accept No Reject no EAP |
145
|
|
|
-36 - Reject No Accept no EAP |
146
|
|
|
-37 - Reject No EAP Accept no EAP |
147
|
|
|
-40 - UDP test error |
148
|
|
|
|
149
|
|
|
*/ |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* ask the monitoring API about the things it knows |
153
|
|
|
* |
154
|
|
|
* @param string $type which type of test to execute |
155
|
|
|
* @param string $param1 test-specific parameter number 1, if any |
156
|
|
|
* @param string $param2 test-specific parameter number 2, if any |
157
|
|
|
* @return array |
158
|
|
|
*/ |
159
|
|
|
private function genericAPIStatus($type, $param1 = NULL, $param2 = NULL) |
160
|
|
|
{ |
161
|
|
|
$endpoints = [ |
162
|
|
|
'tlr_test' => "https://monitor.eduroam.org/mapi/index.php?type=tlr_test&tlr=$param1", |
163
|
|
|
'federation_via_tlr' => "https://monitor.eduroam.org/mapi/index.php?type=federation_via_tlr&federation=$param1", |
164
|
|
|
'flrs_test' => "https://monitor.eduroam.org/mapi/index.php?type=flrs_test&federation=$param1", |
165
|
|
|
'flr_by_federation' => "https://monitor.eduroam.org/mapi/index.php?type=flr_by_federation&federation=$param2&with=$param1", |
166
|
|
|
]; |
167
|
|
|
$ignore = [ |
168
|
|
|
'tlr_test' => 'tlr', |
169
|
|
|
'federation_via_tlr' => 'fed', |
170
|
|
|
'flrs_test' => 'fed', |
171
|
|
|
'flr_by_federation' => 'fed', |
172
|
|
|
]; |
173
|
|
|
$this->loggerInstance->debug(4, "Doing Monitoring API check with $endpoints[$type]\n"); |
174
|
|
|
$jsonResult = \core\common\OutsideComm::downloadFile($endpoints[$type]); |
175
|
|
|
$this->loggerInstance->debug(4, "Monitoring API Result: $jsonResult\n"); |
176
|
|
|
$retval = []; |
177
|
|
|
if ($jsonResult === FALSE) { // monitoring API didn't respond at all! |
178
|
|
|
$retval["STATUS"] = AbstractTest::STATUS_MONITORINGFAIL; |
179
|
|
|
return $retval; |
180
|
|
|
} |
181
|
|
|
$decoded = json_decode($jsonResult, TRUE); |
182
|
|
|
$retval["RAW"] = $decoded; |
183
|
|
|
$atLeastOneFunctional = FALSE; |
184
|
|
|
$allFunctional = TRUE; |
185
|
|
|
if (!isset($decoded[$type]) || isset($decoded['ERROR'])) { |
186
|
|
|
$retval["STATUS"] = AbstractTest::STATUS_MONITORINGFAIL; |
187
|
|
|
return $retval; |
188
|
|
|
} |
189
|
|
|
foreach ($decoded[$type] as $instance => $resultset) { |
190
|
|
|
if ($instance == $ignore[$type]) { |
191
|
|
|
// don't care |
192
|
|
|
continue; |
193
|
|
|
} |
194
|
|
|
// TLR test has statuscode on this level, otherwise need to recurse |
195
|
|
|
// one more level |
196
|
|
|
switch ($type) { |
197
|
|
|
case "tlr_test": |
198
|
|
|
switch ($resultset['status_code']) { |
199
|
|
|
case 0: |
200
|
|
|
$atLeastOneFunctional = TRUE; |
201
|
|
|
break; |
202
|
|
|
case 9: // monitoring itself has an error, no effect on our verdict |
203
|
|
|
case -1: // Reject test fails, but we diagnose supposed-working connection, so no effect on our verdict |
204
|
|
|
case -10: // same as previous |
205
|
|
|
case -11: // same as previous |
206
|
|
|
break; |
207
|
|
|
default: |
208
|
|
|
$allFunctional = FALSE; |
209
|
|
|
} |
210
|
|
|
break; |
211
|
|
|
default: |
212
|
|
|
foreach ($resultset as $particularInstance => $particularResultset) { |
213
|
|
|
switch ($particularResultset['status_code']) { |
214
|
|
|
case 0: |
215
|
|
|
$atLeastOneFunctional = TRUE; |
216
|
|
|
break; |
217
|
|
|
case 9: // monitoring itself has an error, no effect on our verdict |
218
|
|
|
case -1: // Reject test fails, but we diagnose supposed-working connection, so no effect on our verdict |
219
|
|
|
case -10: // same as previous |
220
|
|
|
case -11: // same as previous |
221
|
|
|
break; |
222
|
|
|
default: |
223
|
|
|
$allFunctional = FALSE; |
224
|
|
|
} |
225
|
|
|
} |
226
|
|
|
} |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
if ($allFunctional) { |
230
|
|
|
$retval["STATUS"] = AbstractTest::STATUS_GOOD; |
231
|
|
|
return $retval; |
232
|
|
|
} |
233
|
|
|
if ($atLeastOneFunctional) { |
234
|
|
|
$retval["STATUS"] = AbstractTest::STATUS_PARTIAL; |
235
|
|
|
return $retval; |
236
|
|
|
} |
237
|
|
|
$retval["STATUS"] = AbstractTest::STATUS_DOWN; |
238
|
|
|
return $retval; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* Are the ETLR servers in order? |
243
|
|
|
* @return array |
244
|
|
|
*/ |
245
|
|
|
private function checkEtlrStatus() |
246
|
|
|
{ |
247
|
|
|
// TODO: we always check the European TLRs even though the connection in question might go via others and/or this one |
248
|
|
|
// needs a table to determine what goes where :-( |
249
|
|
|
$ret = $this->genericAPIStatus("tlr_test", "TLR_EU"); |
250
|
|
|
$this->additionalFindings[AbstractTest::INFRA_ETLR][] = $ret; |
251
|
|
|
switch ($ret["STATUS"]) { |
252
|
|
|
case AbstractTest::STATUS_GOOD: |
253
|
|
|
unset($this->possibleFailureReasons[AbstractTest::INFRA_ETLR]); |
254
|
|
|
break; |
255
|
|
|
case AbstractTest::STATUS_PARTIAL: |
256
|
|
|
case AbstractTest::STATUS_MONITORINGFAIL: |
257
|
|
|
// one of the ETLRs is down, or there is a failure in the monitoring system? |
258
|
|
|
// This probably doesn't impact the user unless he's unlucky and has his session fall into failover. |
259
|
|
|
// keep ETLR as a possible problem with original probability |
260
|
|
|
break; |
261
|
|
|
case AbstractTest::STATUS_DOWN: |
262
|
|
|
// Oh! Well if it is not international roaming, that still doesn't have an effect /in this case/. |
263
|
|
|
if ($this->idPFederation == $this->visitedFlr) { |
264
|
|
|
unset($this->possibleFailureReasons[AbstractTest::INFRA_ETLR]); |
265
|
|
|
break; |
266
|
|
|
} |
267
|
|
|
// But it is about int'l roaming, and we are spot on here. |
268
|
|
|
// Raise probability by much (even monitoring is sometimes wrong, or a few minutes behind reality) |
269
|
|
|
$this->possibleFailureReasons[AbstractTest::INFRA_ETLR] = 0.95; |
270
|
|
|
} |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* Is the uplink between an NRO server and the ETLRs in order? |
275
|
|
|
* @param string $whichSide test towards the IdP or SP side? |
276
|
|
|
* @return array |
277
|
|
|
* @throws Exception |
278
|
|
|
*/ |
279
|
|
|
private function checkFedEtlrUplink($whichSide) |
280
|
|
|
{ |
281
|
|
|
// TODO: we always check the European TLRs even though the connection in question might go via others and/or this one |
282
|
|
|
// needs a table to determine what goes where :-( |
283
|
|
|
switch ($whichSide) { |
284
|
|
|
case AbstractTest::INFRA_NRO_IDP: |
285
|
|
|
$fed = $this->idPFederation; |
286
|
|
|
$linkVariant = AbstractTest::INFRA_LINK_ETLR_NRO_IDP; |
287
|
|
|
break; |
288
|
|
|
case AbstractTest::INFRA_NRO_SP: |
289
|
|
|
$fed = $this->visitedFlr; |
290
|
|
|
$linkVariant = AbstractTest::INFRA_LINK_ETLR_NRO_SP; |
291
|
|
|
break; |
292
|
|
|
default: |
293
|
|
|
throw new Exception("This function operates on the IdP- or SP-side FLR, nothing else!"); |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
$ret = $this->genericAPIStatus("federation_via_tlr", $fed); |
297
|
|
|
$this->additionalFindings[AbstractTest::INFRA_NRO_IDP][] = $ret; |
298
|
|
|
switch ($ret["STATUS"]) { |
299
|
|
|
case AbstractTest::STATUS_GOOD: |
300
|
|
|
unset($this->possibleFailureReasons[$whichSide]); |
301
|
|
|
unset($this->possibleFailureReasons[$linkVariant]); |
302
|
|
|
break; |
303
|
|
|
case AbstractTest::STATUS_PARTIAL: |
304
|
|
|
// a subset of the FLRs is down? This probably doesn't impact the user unless he's unlucky and has his session fall into failover. |
305
|
|
|
// keep FLR as a possible problem with original probability |
306
|
|
|
break; |
307
|
|
|
case AbstractTest::STATUS_DOWN: |
308
|
|
|
// Raise probability by much (even monitoring is sometimes wrong, or a few minutes behind reality) |
309
|
|
|
// if earlier test found the server itself to be the problem, keep it, otherwise put the blame on the link |
310
|
|
|
if ($this->possibleFailureReasons[$whichSide] != 0.95) { |
311
|
|
|
$this->possibleFailureReasons[$linkVariant] = 0.95; |
312
|
|
|
} |
313
|
|
|
} |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Is the NRO server itself in order? |
318
|
|
|
* @param string $whichSide test towards the IdP or SP side? |
319
|
|
|
* @return array |
320
|
|
|
* @throws Exception |
321
|
|
|
*/ |
322
|
|
|
private function checkFlrServerStatus($whichSide) |
323
|
|
|
{ |
324
|
|
|
switch ($whichSide) { |
325
|
|
|
case AbstractTest::INFRA_NRO_IDP: |
326
|
|
|
$fed = $this->idPFederation; |
327
|
|
|
break; |
328
|
|
|
case AbstractTest::INFRA_NRO_SP: |
329
|
|
|
$fed = $this->visitedFlr; |
330
|
|
|
break; |
331
|
|
|
default: |
332
|
|
|
throw new Exception("This function operates on the IdP- or SP-side FLR, nothing else!"); |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
$ret = $this->genericAPIStatus("flrs_test", $fed); |
336
|
|
|
$this->additionalFindings[$whichSide][] = $ret; |
337
|
|
|
switch ($ret["STATUS"]) { |
338
|
|
|
case AbstractTest::STATUS_GOOD: |
339
|
|
|
unset($this->possibleFailureReasons[$whichSide]); |
340
|
|
|
break; |
341
|
|
|
case AbstractTest::STATUS_PARTIAL: |
342
|
|
|
// a subset of the FLRs is down? This probably doesn't impact the user unless he's unlucky and has his session fall into failover. |
343
|
|
|
// keep FLR as a possible problem with original probability |
344
|
|
|
break; |
345
|
|
|
case AbstractTest::STATUS_DOWN: |
346
|
|
|
// Raise probability by much (even monitoring is sometimes wrong, or a few minutes behind reality) |
347
|
|
|
$this->possibleFailureReasons[$whichSide] = 0.95; |
348
|
|
|
} |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
/** |
352
|
|
|
* Does authentication traffic flow between a given source and destination NRO? |
353
|
|
|
* @return array |
354
|
|
|
*/ |
355
|
|
|
private function checkNROFlow() |
356
|
|
|
{ |
357
|
|
|
return $this->genericAPIStatus("flr_by_federation", $this->idPFederation, $this->visitedFlr); |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
/** |
361
|
|
|
* Runs the CAT-internal diagnostics tests. Determines the state of the |
362
|
|
|
* realm (and indirectly that of the links and statuses of involved proxies |
363
|
|
|
* and returns a judgment whether external Monitoring API tests are warranted |
364
|
|
|
* or not |
365
|
|
|
* @return boolean TRUE if external tests have to be run |
366
|
|
|
*/ |
367
|
|
|
private function CATInternalTests() |
368
|
|
|
{ |
369
|
|
|
// we are expecting to get a REJECT from all runs, because that means the packet got through to the IdP. |
370
|
|
|
// (the ETLR sometimes does a "Reject instead of Ignore" but that is filtered out and changed into a timeout |
371
|
|
|
// by the test suite automatically, so it does not disturb the measurement) |
372
|
|
|
// If that's true, we can exclude two sources of problems (both proxy levels). Hooray! |
373
|
|
|
$allAreConversationReject = TRUE; |
374
|
|
|
$atLeastOneConversationReject = FALSE; |
375
|
|
|
|
376
|
|
|
foreach (\config\Diagnostics::RADIUSTESTS['UDP-hosts'] as $probeindex => $probe) { |
377
|
|
|
$reachCheck = $this->testsuite->udpReachability($probeindex); |
378
|
|
|
if ($reachCheck != RADIUSTests::RETVAL_CONVERSATION_REJECT) { |
379
|
|
|
$allAreConversationReject = FALSE; |
380
|
|
|
} else { |
381
|
|
|
$atLeastOneConversationReject = TRUE; |
382
|
|
|
} |
383
|
|
|
|
384
|
|
|
$this->additionalFindings[AbstractTest::INFRA_ETLR][] = ["DETAIL" => $this->testsuite->consolidateUdpResult($probeindex)]; |
385
|
|
|
$this->additionalFindings[AbstractTest::INFRA_NRO_IDP][] = ["DETAIL" => $this->testsuite->consolidateUdpResult($probeindex)]; |
386
|
|
|
$this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS][] = ["DETAIL" => $this->testsuite->consolidateUdpResult($probeindex)]; |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
if ($allAreConversationReject) { |
390
|
|
|
$this->additionalFindings[AbstractTest::INFRA_ETLR][] = ["CONNCHECK" => RADIUSTests::RETVAL_CONVERSATION_REJECT]; |
391
|
|
|
$this->additionalFindings[AbstractTest::INFRA_NRO_IDP][] = ["CONNCHECK" => RADIUSTests::RETVAL_CONVERSATION_REJECT]; |
392
|
|
|
$this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS][] = ["CONNCHECK" => RADIUSTests::RETVAL_CONVERSATION_REJECT]; |
393
|
|
|
$this->additionalFindings[AbstractTest::INFRA_LINK_ETLR_NRO_IDP][] = ["LINKCHECK" => RADIUSTests::L_OK]; |
394
|
|
|
// we have actually reached an IdP, so all links are good, and the |
395
|
|
|
// realm is routable in eduroam. So even if it exists in neither DB |
396
|
|
|
// we can exclude the NONEXISTENTREALM case |
397
|
|
|
unset($this->possibleFailureReasons[AbstractTest::INFRA_ETLR]); |
398
|
|
|
unset($this->possibleFailureReasons[AbstractTest::INFRA_NRO_IDP]); |
399
|
|
|
unset($this->possibleFailureReasons[AbstractTest::INFRA_LINK_ETLR_NRO_IDP]); |
400
|
|
|
unset($this->possibleFailureReasons[AbstractTest::INFRA_NONEXISTENTREALM]); |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
if ($atLeastOneConversationReject) { |
404
|
|
|
// at least we can be sure it exists |
405
|
|
|
unset($this->possibleFailureReasons[AbstractTest::INFRA_NONEXISTENTREALM]); |
406
|
|
|
// It could still be an IdP RADIUS problem in that some cert oddities |
407
|
|
|
// in combination with the device lead to a broken auth |
408
|
|
|
// if there is nothing beyond the "REMARK" level, then it's not an IdP problem |
409
|
|
|
// otherwise, add the corresponding warnings and errors to $additionalFindings |
410
|
|
|
$highestObservedErrorLevel = 0; |
411
|
|
|
foreach ($this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS] as $oneRun) { |
412
|
|
|
$highestObservedErrorLevel = max($highestObservedErrorLevel, $oneRun['DETAIL']['level'] ?? 0); |
413
|
|
|
} |
414
|
|
|
switch ($highestObservedErrorLevel) { |
415
|
|
|
case RADIUSTests::L_OK: |
416
|
|
|
case RADIUSTests::L_REMARK: |
417
|
|
|
// both are fine - the IdP is working and the user problem |
418
|
|
|
// is not on the IdP RADIUS level |
419
|
|
|
$this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS][] = ["ODDITYLEVEL" => $this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS][0]['DETAIL']['level']]; |
420
|
|
|
unset($this->possibleFailureReasons[AbstractTest::INFRA_IDP_RADIUS]); |
421
|
|
|
break; |
422
|
|
|
case RADIUSTests::L_WARN: |
423
|
|
|
$this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS][] = ["ODDITYLEVEL" => RADIUSTests::L_WARN]; |
424
|
|
|
$this->possibleFailureReasons[AbstractTest::INFRA_IDP_RADIUS] = 0.3; // possibly we found the culprit - if RADIUS server is misconfigured AND user is on a device which reacts picky about exactly this oddity. |
425
|
|
|
break; |
426
|
|
|
case RADIUSTests::L_ERROR: |
427
|
|
|
$this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS][] = ["ODDITYLEVEL" => RADIUSTests::L_ERROR]; |
428
|
|
|
$this->possibleFailureReasons[AbstractTest::INFRA_IDP_RADIUS] = 0.8; // errors are never good, so we can be reasonably sure we've hit the spot! |
429
|
|
|
} |
430
|
|
|
} |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
/** |
434
|
|
|
* can we run thorough checks or not? Thorough can only be done if we can |
435
|
|
|
* deterministically map the realm to be checked against a CAT Profile, and |
436
|
|
|
* then only if the profile is complete. |
437
|
|
|
* |
438
|
|
|
* @return void |
439
|
|
|
*/ |
440
|
|
|
private function determineTestsuiteParameters() |
441
|
|
|
{ |
442
|
|
|
if ($this->catProfile > 0) { |
443
|
|
|
$profileObject = \core\ProfileFactory::instantiate($this->catProfile); |
444
|
|
|
$readinessLevel = $profileObject->readinessLevel(); |
445
|
|
|
switch ($readinessLevel) { |
446
|
|
|
case \core\AbstractProfile::READINESS_LEVEL_SHOWTIME: |
447
|
|
|
// fall-througuh intended: use the data even if non-public but complete |
448
|
|
|
case \core\AbstractProfile::READINESS_LEVEL_SUFFICIENTCONFIG: |
449
|
|
|
$this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS][] = ["Profile" => $profileObject->identifier]; |
450
|
|
|
$outer = $profileObject->getRealmCheckOuterUsername(); |
451
|
|
|
$this->testsuite = new RADIUSTests($this->realm, $outer, $profileObject->getEapMethodsinOrderOfPreference(1), $profileObject->getCollapsedAttributes()['eap:server_name'], $profileObject->getCollapsedAttributes()["eap:ca_file"]); |
452
|
|
|
break; |
453
|
|
|
case \core\AbstractProfile::READINESS_LEVEL_NOTREADY: |
454
|
|
|
$this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS][] = ["Profile" => "UNCONCLUSIVE"]; |
455
|
|
|
$this->testsuite = new RADIUSTests($this->realm, "anonymous@" . $this->realm); |
456
|
|
|
break; |
457
|
|
|
default: |
458
|
|
|
} |
459
|
|
|
} else { |
460
|
|
|
$this->testsuite = new RADIUSTests($this->realm, "anonymous@" . $this->realm); |
461
|
|
|
} |
462
|
|
|
} |
463
|
|
|
|
464
|
|
|
/** |
465
|
|
|
* Produces "the best" testsuite parameters basing on CAT profiles |
466
|
|
|
* |
467
|
|
|
* @return testsuite object |
|
|
|
|
468
|
|
|
*/ |
469
|
|
|
public function getOuter() |
470
|
|
|
{ |
471
|
|
|
if ($this->catProfile > 0) { |
472
|
|
|
$profileObject = \core\ProfileFactory::instantiate($this->catProfile); |
473
|
|
|
$readinessLevel = $profileObject->readinessLevel(); |
474
|
|
|
switch ($readinessLevel) { |
475
|
|
|
case \core\AbstractProfile::READINESS_LEVEL_SHOWTIME: |
476
|
|
|
// fall-througuh intended: use the data even if non-public but complete |
477
|
|
|
case \core\AbstractProfile::READINESS_LEVEL_SUFFICIENTCONFIG: |
478
|
|
|
$this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS][] = ["Profile" => $profileObject->identifier]; |
479
|
|
|
$outer = $profileObject->getRealmCheckOuterUsername(); |
480
|
|
|
$p = strpos($outer, '@'); |
481
|
|
|
if ($p !== false) { |
482
|
|
|
$outer = substr($outer, 0, $p); |
483
|
|
|
} |
484
|
|
|
break; |
485
|
|
|
case \core\AbstractProfile::READINESS_LEVEL_NOTREADY: |
486
|
|
|
$this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS][] = ["Profile" => "UNCONCLUSIVE"]; |
487
|
|
|
break; |
488
|
|
|
default: |
489
|
|
|
} |
490
|
|
|
} else { |
491
|
|
|
$outer = "anonymous"; |
492
|
|
|
} |
493
|
|
|
return $outer; |
|
|
|
|
494
|
|
|
} |
495
|
|
|
/** |
496
|
|
|
* Does the main meditation job |
497
|
|
|
* @return array the findings |
498
|
|
|
*/ |
499
|
|
|
public function magic() |
500
|
|
|
{ |
501
|
|
|
$this->testId = \core\CAT::uuid(); |
502
|
|
|
$this->databaseHandle->exec("INSERT INTO diagnosticrun (test_id, realm, suspects, evidence) VALUES ('$this->testId', '$this->realm', NULL, NULL)"); |
503
|
|
|
// simple things first: do we know anything about the realm, either |
504
|
|
|
// because it's a CAT participant or because it's in the eduroam DB? |
505
|
|
|
// if so, we can exclude the INFRA_NONEXISTENTREALM cause |
506
|
|
|
$this->additionalFindings[AbstractTest::INFRA_NONEXISTENTREALM]['DATABASE_STATUS'] = ["ID1" => $this->catProfile, "ID2" => $this->dbIdP]; |
507
|
|
|
if ($this->catProfile != \core\Federation::UNKNOWN_IDP || $this->dbIdP != \core\Federation::UNKNOWN_IDP) { |
508
|
|
|
unset($this->possibleFailureReasons[AbstractTest::INFRA_NONEXISTENTREALM]); |
509
|
|
|
} |
510
|
|
|
// do we operate on a non-ambiguous, fully configured CAT profile? Then |
511
|
|
|
// we run the more thorough check, otherwise the shallow one. |
512
|
|
|
$this->determineTestSuiteParameters(); |
513
|
|
|
// let's do the least amount of testing needed: |
514
|
|
|
// - The CAT reachability test already covers ELTRs, IdP NRO level and the IdP itself. |
515
|
|
|
// if the realm maps to a CAT IdP, we can run the more thorough tests; otherwise just |
516
|
|
|
// the normal shallow ones |
517
|
|
|
// these are the normal "realm check" tests covering ETLR, LINK_NRO_IDP, NRO, IDP_RADIUS |
518
|
|
|
$this->CATInternalTests(); |
519
|
|
|
// - if the test does NOT go through, we need to find out which of the three is guilty |
520
|
|
|
// - then, the international "via ETLR" check can be used to find out if the IdP alone |
521
|
|
|
// is guilty. If that one fails, the direct monitoring of servers and ETLRs themselves |
522
|
|
|
// closes the loop. |
523
|
|
|
// let's see if the ETLRs are up |
524
|
|
|
if (array_key_exists(AbstractTest::INFRA_ETLR, $this->possibleFailureReasons)) { |
525
|
|
|
$this->checkEtlrStatus(); |
526
|
|
|
} |
527
|
|
|
|
528
|
|
|
// then let's check the IdP's FLR, if we know the IdP federation at all |
529
|
|
|
if ($this->idPFederation !== NULL) { |
530
|
|
|
if (array_key_exists(AbstractTest::INFRA_NRO_IDP, $this->possibleFailureReasons)) { |
531
|
|
|
// first the direct connectivity to the server |
532
|
|
|
$this->checkFlrServerStatus(AbstractTest::INFRA_NRO_IDP); |
533
|
|
|
} |
534
|
|
|
// now let's theck the link |
535
|
|
|
if (array_key_exists(AbstractTest::INFRA_LINK_ETLR_NRO_IDP, $this->possibleFailureReasons)) { |
536
|
|
|
$this->checkFedEtlrUplink(AbstractTest::INFRA_NRO_IDP); |
537
|
|
|
} |
538
|
|
|
} |
539
|
|
|
// now, if we know the country the user is currently in, let's see |
540
|
|
|
// if the NRO SP-side is up |
541
|
|
|
if ($this->visitedFlr !== NULL) { |
542
|
|
|
$this->checkFlrServerStatus(AbstractTest::INFRA_NRO_SP); |
543
|
|
|
// and again its uplink to the ETLR |
544
|
|
|
$this->checkFedEtlrUplink(AbstractTest::INFRA_NRO_SP); |
545
|
|
|
} |
546
|
|
|
// the last thing we can do (but it's a bit redundant): check the country-to-country link |
547
|
|
|
// it's only needed if all three and their links are up, but we want to exclude funny routing blacklists |
548
|
|
|
// which occur only in the *combination* of source and dest |
549
|
|
|
// if there is an issue at that point, blame the SP: once a request |
550
|
|
|
// would have reached the ETLRs, things would be all good (assuming |
551
|
|
|
// perfection on the ETLRs here!). So the SP has a wrong config. |
552
|
|
|
if ($this->idPFederation !== NULL && |
553
|
|
|
$this->visitedFlr !== NULL && |
554
|
|
|
!array_key_exists(AbstractTest::INFRA_ETLR, $this->possibleFailureReasons) && |
555
|
|
|
!array_key_exists(AbstractTest::INFRA_LINK_ETLR_NRO_IDP, $this->possibleFailureReasons) && |
556
|
|
|
!array_key_exists(AbstractTest::INFRA_NRO_IDP, $this->possibleFailureReasons) && |
557
|
|
|
!array_key_exists(AbstractTest::INFRA_LINK_ETLR_NRO_SP, $this->possibleFailureReasons) && |
558
|
|
|
!array_key_exists(AbstractTest::INFRA_NRO_SP, $this->possibleFailureReasons) |
559
|
|
|
) { |
560
|
|
|
$countryToCountryStatus = $this->checkNROFlow(); |
561
|
|
|
$this->additionalFindings[AbstractTest::INFRA_NRO_SP][] = $countryToCountryStatus; |
562
|
|
|
$this->additionalFindings[AbstractTest::INFRA_ETLR][] = $countryToCountryStatus; |
563
|
|
|
$this->additionalFindings[AbstractTest::INFRA_NRO_IDP][] = $countryToCountryStatus; |
564
|
|
|
switch ($countryToCountryStatus["STATUS"]) { |
565
|
|
|
case AbstractTest::STATUS_GOOD: |
566
|
|
|
// all routes work |
567
|
|
|
break; |
568
|
|
|
case AbstractTest::STATUS_PARTIAL: |
569
|
|
|
// at least one, or even all have a routing problem |
570
|
|
|
case AbstractTest::STATUS_DOWN: |
571
|
|
|
// that's rather telling. |
572
|
|
|
$this->possibleFailureReasons[AbstractTest::INFRA_NRO_SP] = 0.95; |
573
|
|
|
} |
574
|
|
|
} |
575
|
|
|
$this->normaliseResultSet(); |
576
|
|
|
$jsonSuspects = json_encode($this->possibleFailureReasons, JSON_PRETTY_PRINT); |
577
|
|
|
$jsonEvidence = json_encode($this->additionalFindings, JSON_PRETTY_PRINT); |
578
|
|
|
$this->databaseHandle->exec("UPDATE diagnosticrun SET realm = ?, visited_flr = ?, visited_hotspot = ?, suspects = ?, evidence = ? WHERE test_id = ?", "ssssss", $this->realm, $this->visitedFlr, $this->visitedHotspot, $jsonSuspects, $jsonEvidence, $this->testId); |
579
|
|
|
$_SESSION['TESTID'] = $this->testId; |
580
|
|
|
$_SESSION["SUSPECTS"] = $this->possibleFailureReasons; |
581
|
|
|
$_SESSION["EVIDENCE"] = $this->additionalFindings; |
582
|
|
|
return ["SUSPECTS" => $this->possibleFailureReasons, "EVIDENCE" => $this->additionalFindings]; |
583
|
|
|
} |
584
|
|
|
} |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths