|
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