Test Failed
Push — master ( e3ff0d...7df8e1 )
by Maja
08:30 queued 16s
created

DeploymentManaged::retrieveStatistics()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 9
rs 10
cc 1
nc 1
nop 1
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
/**
24
 * This file contains the AbstractProfile class. It contains common methods for
25
 * both RADIUS/EAP profiles and SilverBullet profiles
26
 *
27
 * @author Stefan Winter <[email protected]>
28
 * @author Tomasz Wolniewicz <[email protected]>
29
 *
30
 * @package Developer
31
 *
32
 */
33
34
namespace core;
35
36
use \Exception;
37
38
/**
39
 * This class represents an EAP Profile.
40
 * Profiles can inherit attributes from their IdP, if the IdP has some. Otherwise,
41
 * one can set attribute in the Profile directly. If there is a conflict between
42
 * IdP-wide and Profile-wide attributes, the more specific ones (i.e. Profile) win.
43
 * 
44
 * @author Stefan Winter <[email protected]>
45
 * @author Tomasz Wolniewicz <[email protected]>
46
 *
47
 * @license see LICENSE file in root directory
48
 *
49
 * @package Developer
50
 */
51
class DeploymentManaged extends AbstractDeployment
52
{
53
54
    /**
55
     * This is the limit for dual-stack hosts. Single stack uses half of the FDs
56
     * in FreeRADIUS and take twice as many. initialise() takes this into
57
     * account.
58
     */
59
    const MAX_CLIENTS_PER_SERVER = 200;
60
    const PRODUCTNAME = "Managed SP";
61
62
    /**
63
     * the primary RADIUS server port for this SP instance
64
     * 
65
     * @var integer
66
     */
67
    public $port1;
68
69
    /**
70
     * the backup RADIUS server port for this SP instance
71
     * 
72
     * @var integer
73
     */
74
    public $port2;
75
76
    /**
77
     * the shared secret for this SP instance
78
     * 
79
     * @var string
80
     */
81
    public $secret;
82
83
    /**
84
     * the IPv4 address of the primary RADIUS server for this SP instance 
85
     * (can be NULL)
86
     * 
87
     * @var string
88
     */
89
    public $host1_v4;
90
91
    /**
92
     * the IPv6 address of the primary RADIUS server for this SP instance 
93
     * (can be NULL)
94
     * 
95
     * @var string
96
     */
97
    public $host1_v6;
98
99
    /**
100
     * the IPv4 address of the backup RADIUS server for this SP instance 
101
     * (can be NULL)
102
     * 
103
     * @var string
104
     */
105
    public $host2_v4;
106
107
    /**
108
     * the IPv6 address of the backup RADIUS server for this SP instance 
109
     * (can be NULL)
110
     * 
111
     * @var string
112
     */
113
    public $host2_v6;
114
115
    /**
116
     * the primary RADIUS server instance for this SP instance
117
     * 
118
     * @var string
119
     */
120
    public $radius_instance_1;
121
122
    /**
123
     * the backup RADIUS server instance for this SP instance
124
     * 
125
     * @var string
126
     */
127
    public $radius_instance_2;
128
129
    /**
130
     * the primary RADIUS server hostname - for sending configuration requests
131
     * 
132
     * @var string
133
     */
134
    public $radius_hostname_1;
135
136
    /**
137
     * the backup RADIUS server hostname - for sending configuration requests
138
     * 
139
     * @var string
140
     */
141
    public $radius_hostname_2;
142
143
    /**
144
     * the primary RADIUS server status - last configuration request result
145
     * 
146
     * @var string
147
     */
148
    public $radius_status_1;
149
150
    /**
151
     * the backup RADIUS server status - last configuration request result
152
     * 
153
     * @var string
154
     */
155
    public $radius_status_2;
156
    
157
    /**
158
     * the consortium this deployment is attached to
159
     * 
160
     * @var string
161
     */
162
    public $consortium;
163
164
    /**
165
     * the T&C of this service
166
     */
167
    public $termsAndConditions;
168
    
169
    /**
170
     * Class constructor for existing deployments (use 
171
     * IdP::newDeployment() to actually create one). Retrieves all 
172
     * attributes from the DB and stores them in the priv_ arrays.
173
     * 
174
     * @param IdP        $idpObject       optionally, the institution to which this Profile belongs. Saves the construction of the IdP instance. If omitted, an extra query and instantiation is executed to find out.
175
     * @param string|int $deploymentIdRaw identifier of the deployment in the DB
176
     * @param string     $consortium      identifier of the consortium to attach to (only relevant when initialising a new deployment for the first time
177
     * @throws Exception
178
     */
179
    public function __construct($idpObject, $deploymentIdRaw, $consortium = 'eduroam')
180
    {
181
        parent::__construct($idpObject, $deploymentIdRaw); // we now have access to our INST database handle and logging
182
        $this->entityOptionTable = "deployment_option";
183
        $this->entityIdColumn = "deployment_id";
184
        $this->type = AbstractDeployment::DEPLOYMENTTYPE_MANAGED;
185
        if (!is_numeric($deploymentIdRaw)) {
186
            throw new Exception("Managed SP instances have to have a numeric identifier");
187
        }
188
        $propertyQuery = "SELECT consortium, status,port_instance_1,port_instance_2,secret,radius_instance_1,radius_instance_2,radius_status_1,radius_status_2,consortium FROM deployment WHERE deployment_id = ?";
189
        $queryExec = $this->databaseHandle->exec($propertyQuery, "i", $deploymentIdRaw);
190
        if (mysqli_num_rows(/** @scrutinizer ignore-type */ $queryExec) == 0) {
191
            throw new Exception("Attempt to construct an unknown DeploymentManaged!");
192
        }
193
        $this->identifier = $deploymentIdRaw;
194
        while ($iterator = mysqli_fetch_object(/** @scrutinizer ignore-type */ $queryExec)) {
195
            if ($iterator->secret == NULL && $iterator->radius_instance_1 == NULL) {
196
                // we are instantiated for the first time, or all previous init attempts failed - so initialise us
197
                // first time: note the consortium, permanently
198
                if ($iterator->consortium === NULL) {
199
                    $this->consortium = $consortium;
200
                    $consortiumQuery = "UPDATE deployment SET consortium = '$this->consortium' WHERE deployment_id = ?";
201
                    $this->databaseHandle->exec($consortiumQuery, "i", $deploymentIdRaw);
202
                }
203
                $details = $this->initialise();
204
                $this->port1 = $details["port_instance_1"];
205
                $this->port2 = $details["port_instance_2"];
206
                $this->secret = $details["secret"];
207
                $this->radius_instance_1 = $details["radius_instance_1"];
208
                $this->radius_instance_2 = $details["radius_instance_2"];
209
                $this->radius_status_1 = 1;
210
                $this->radius_status_2 = 1;
211
                $this->status = AbstractDeployment::INACTIVE;
212
            } else {
213
                $this->port1 = $iterator->port_instance_1;
214
                $this->port2 = $iterator->port_instance_2;
215
                $this->secret = $iterator->secret;
216
                $this->radius_instance_1 = $iterator->radius_instance_1;
217
                $this->radius_instance_2 = $iterator->radius_instance_2;
218
                $this->radius_status_1 = $iterator->radius_status_1;
219
                $this->radius_status_2 = $iterator->radius_status_2;
220
                $this->status = $iterator->status;
221
                $this->consortium = $iterator->consortium;
222
            }
223
        }
224
        $server1details = $this->databaseHandle->exec("SELECT mgmt_hostname, radius_ip4, radius_ip6 FROM managed_sp_servers WHERE server_id = '$this->radius_instance_1'");
225
        while ($iterator2 = mysqli_fetch_object(/** @scrutinizer ignore-type */ $server1details)) {
226
            $this->host1_v4 = $iterator2->radius_ip4;
227
            $this->host1_v6 = $iterator2->radius_ip6;
228
            $this->radius_hostname_1 = $iterator2->mgmt_hostname;
229
        }
230
        $server2details = $this->databaseHandle->exec("SELECT mgmt_hostname, radius_ip4, radius_ip6 FROM managed_sp_servers WHERE server_id = '$this->radius_instance_2'");
231
        while ($iterator3 = mysqli_fetch_object(/** @scrutinizer ignore-type */ $server2details)) {
232
            $this->host2_v4 = $iterator3->radius_ip4;
233
            $this->host2_v6 = $iterator3->radius_ip6;
234
            $this->radius_hostname_2 = $iterator3->mgmt_hostname;
235
        }
236
        $thisLevelAttributes = $this->retrieveOptionsFromDatabase("SELECT DISTINCT option_name, option_lang, option_value, row 
237
                                            FROM $this->entityOptionTable
238
                                            WHERE $this->entityIdColumn = ?  
239
                                            ORDER BY option_name", "Profile");
240
        $tempAttribMergedIdP = $this->levelPrecedenceAttributeJoin($thisLevelAttributes, $this->idpAttributes, "IdP");
241
        $this->attributes = $this->levelPrecedenceAttributeJoin($tempAttribMergedIdP, $this->fedAttributes, "FED");
242
        
243
        $this->termsAndConditions = "<h2>Product Definition</h2>
244
<p>eduroam Managed SP enables eligible institutions to outsource the technical setup of the roaming uplink functions in an eduroam SP to the eduroam Operations Team. eduroam SP administrators use the service instead of a local RADIUS infrastructure. A unique selling point of this service is that the typical limitation of being required to have a static IP address is waived - eduroam SP Wi-Fi infrastructure can be managed by the system even on IP connectivity with changing IP addresses (e.g. DSL, mobile networks).</p>
245
<p>The service includes:</p>
246
<ul><li>web-based user management interface where eduroam SP deployment details can be created and deleted;</li>
247
    <li>web-based institution management interface where institutions are enabled or disabled to use the service;</li>
248
    <li>technical infrastructure ('RADIUS') which accepts RADIUS/UDP requests independently of client IP addresses, processes them according to eduroam Service Definition best practices, and forwards them to eduroam IdPs via the established eduroam roaming infrastructure.</li>
249
</ul>
250
<p>The aspects of eduroam SP operation beyond the RADIUS uplink remain in the responsibility of the eduroam SP administrator, and are subject to the eduroam Service Definition as usual. This includes (but is not limited to) local logging of IP leases to MAC addresses in the Enterprise Wi-Fi session, having sufficient Wi-Fi coverage, and making sure the IP uplink works within expected parameters.</p>
251
<h2>Terms of Use</h2>
252
<p>In addition to the provisions in your participation agreement with your NRO and the eduroam Service Definition, you undertake to keep confidential the RADIUS server uplink details you receive here (which consist of the tuple IP address, port and shared secret).";
253
    }
254
255
    /**
256
     * finds a suitable server which is geographically close to the admin
257
     * 
258
     * @param array  $adminLocation      the current geographic position of the admin
259
     * @param string $federation         the federation this deployment belongs to
260
     * @param array  $blacklistedServers list of server to IGNORE
261
     * @return string the server ID
262
     * @throws Exception
263
     */
264
    private function findGoodServerLocation($adminLocation, $federation, $blacklistedServers)
265
    {
266
        // find a server near him (list of all servers with capacity, ordered by distance)
267
        // first, if there is a pool of servers specifically for this federation, prefer it
268
        // only check the consortium pool group we want to attach to
269
        $servers = $this->databaseHandle->exec("SELECT server_id, radius_ip4, radius_ip6, location_lon, location_lat FROM managed_sp_servers WHERE pool = '$federation' AND consortium = '$this->consortium'");
270
271
        $serverCandidates = [];
272
        while ($iterator = mysqli_fetch_object(/** @scrutinizer ignore-type */ $servers)) {
273
            $maxSupportedClients = DeploymentManaged::MAX_CLIENTS_PER_SERVER;
274
            if ($iterator->radius_ip4 == NULL || $iterator->radius_ip6 == NULL) {
275
                // half the amount of IP stacks means half the amount of FDs in use, so we can take twice as many
276
                $maxSupportedClients = $maxSupportedClients * 2;
277
            }
278
            $clientCount1 = $this->databaseHandle->exec("SELECT port_instance_1 AS tenants1 FROM deployment WHERE radius_instance_1 = '$iterator->server_id'");
279
            $clientCount2 = $this->databaseHandle->exec("SELECT port_instance_2 AS tenants2 FROM deployment WHERE radius_instance_2 = '$iterator->server_id'");
280
281
            $clients = $clientCount1->num_rows + $clientCount2->num_rows;
282
            if (in_array($iterator->server_id, $blacklistedServers)) {
283
                continue;
284
            }
285
            if ($clients < $maxSupportedClients) {
286
                $serverCandidates[IdPlist::geoDistance($adminLocation, ['lat' => $iterator->location_lat, 'lon' => $iterator->location_lon])] = $iterator->server_id;
287
            }
288
            if ($clients > $maxSupportedClients * 0.9) {
289
                $this->loggerInstance->debug(1, "A RADIUS server for Managed SP (" . $iterator->server_id . ") is serving at more than 90% capacity!");
290
            }
291
        }
292
        if (count($serverCandidates) == 0 && $federation != "DEFAULT") {
293
            // we look in the default pool instead
294
            // recursivity! Isn't that cool!
295
            return $this->findGoodServerLocation($adminLocation, "DEFAULT", $blacklistedServers);
296
        }
297
        if (count($serverCandidates) == 0) {
298
            throw new Exception("No available server found for new SP in $federation!");
299
        }
300
        // put the nearest server on top of the list
301
        ksort($serverCandidates);
302
        $this->loggerInstance->debug(1, $serverCandidates);
303
        return array_shift($serverCandidates);
304
    }
305
306
    /**
307
     * retrieves usage statistics for the deployment
308
     * 
309
     * @param int backlog how many seconds back in time should we look
0 ignored issues
show
Bug introduced by
The type core\backlog was not found. Maybe you did not declare it correctly or list all dependencies?

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:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
310
     * @return array of arrays: activity_time, realm, mac, result
311
     * @throws Exception
312
     */
313
    public function retrieveStatistics(int $backlog)
314
    {
315
        // find a server near him (list of all servers with capacity, ordered by distance)
316
        // first, if there is a pool of servers specifically for this federation, prefer it
317
        // only check the consortium pool group we want to attach to
318
        // TODO: if we also collect stats from OpenRoaming hosts, differentiate the logs!
319
        $opName = $this->getOperatorName();
320
        $stats = $this->databaseHandle->exec("SELECT activity_time, realm, mac, result FROM activity WHERE operatorname = ? AND activity_time > DATE_SUB(NOW(), INTERVAL ? SECOND) ORDER BY activity_time", "si", $opName, $backlog );
321
        return mysqli_fetch_all($stats, \MYSQLI_ASSOC);
322
    }
323
        
324
    /**
325
     * initialises a new SP
326
     * 
327
     * @return array details of the SP as generated during initialisation
328
     * @throws Exception
329
     */
330
    private function initialise()
331
    {
332
        // find out where the admin is located approximately
333
        $ourLocation = ['lon' => 0, 'lat' => 0];
334
        $geoip = DeviceLocation::locateDevice();
335
        if ($geoip['status'] == 'ok') {
336
            $ourLocation = ['lon' => $geoip['geo']['lon'], 'lat' => $geoip['geo']['lat']];
337
        }
338
        $inst = new IdP($this->institution);
339
        $ourserver = $this->findGoodServerLocation($ourLocation, $inst->federation, []);
340
        // now, find an unused port in the preferred server
341
        $foundFreePort1 = 0;
342
        while ($foundFreePort1 == 0) {
343
            $portCandidate = random_int(1200, 65535);
344
            $check = $this->databaseHandle->exec("SELECT port_instance_1 FROM deployment WHERE radius_instance_1 = '" . $ourserver . "' AND port_instance_1 = $portCandidate");
345
            if (mysqli_num_rows(/** @scrutinizer ignore-type */ $check) == 0) {
346
                $foundFreePort1 = $portCandidate;
347
            }
348
        }
349
        $ourSecondServer = $this->findGoodServerLocation($ourLocation, $inst->federation, [$ourserver]);
350
        $foundFreePort2 = 0;
351
        while ($foundFreePort2 == 0) {
352
            $portCandidate = random_int(1200, 65535);
353
            $check = $this->databaseHandle->exec("SELECT port_instance_2 FROM deployment WHERE radius_instance_2 = '" . $ourSecondServer . "' AND port_instance_2 = $portCandidate");
354
            if (mysqli_num_rows(/** @scrutinizer ignore-type */ $check) == 0) {
355
                $foundFreePort2 = $portCandidate;
356
            }
357
        }
358
        // and make up a shared secret that is halfways readable
359
        $futureSecret = $this->randomString(16, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
360
        $this->databaseHandle->exec("UPDATE deployment SET radius_instance_1 = '" . $ourserver . "', radius_instance_2 = '" . $ourSecondServer . "', port_instance_1 = $foundFreePort1, port_instance_2 = $foundFreePort2, secret = '$futureSecret', consortium = '$this->consortium' WHERE deployment_id = $this->identifier");
361
        return ["port_instance_1" => $foundFreePort1, "port_instance_2" => $foundFreePort2, "secret" => $futureSecret, "radius_instance_1" => $ourserver, "radius_instance_2" => $ourserver];
362
    }
363
364
    /**
365
     * update the last_changed timestamp for this deployment
366
     * 
367
     * @return void
368
     */
369
    public function updateFreshness()
370
    {
371
        $this->databaseHandle->exec("UPDATE deployment SET last_change = CURRENT_TIMESTAMP WHERE deployment_id = $this->identifier");
372
    }
373
374
    /**
375
     * gets the last-modified timestamp (useful for caching "dirty" check)
376
     * 
377
     * @return string the date in string form, as returned by SQL
378
     */
379
    public function getFreshness()
380
    {
381
        $execLastChange = $this->databaseHandle->exec("SELECT last_change FROM deployment WHERE deployment_id = $this->identifier");
382
        // SELECT always returns a resource, never a boolean
383
        if ($freshnessQuery = mysqli_fetch_object(/** @scrutinizer ignore-type */ $execLastChange)) {
384
            return $freshnessQuery->last_change;
385
        }
386
    }
387
388
    /**
389
     * Deletes the deployment from database
390
     * 
391
     * @return void
392
     */
393
    public function destroy()
394
    {
395
        $this->databaseHandle->exec("DELETE FROM deployment_option WHERE deployment_id = $this->identifier");
396
        $this->databaseHandle->exec("DELETE FROM deployment WHERE deployment_id = $this->identifier");
397
    }
398
399
    /**
400
     * deactivates the deployment.
401
     * TODO: needs to call the RADIUS server reconfiguration routines...
402
     * 
403
     * @return void
404
     */
405
    public function deactivate()
406
    {
407
        $this->databaseHandle->exec("UPDATE deployment SET status = " . DeploymentManaged::INACTIVE . " WHERE deployment_id = $this->identifier");
408
    }
409
410
    /**
411
     * activates the deployment.
412
     * TODO: needs to call the RADIUS server reconfiguration routines...
413
     * 
414
     * @return void
415
     */
416
    public function activate()
417
    {
418
        $this->databaseHandle->exec("UPDATE deployment SET status = " . DeploymentManaged::ACTIVE . " WHERE deployment_id = $this->identifier");
419
    }
420
421
    /**
422
     * determines the Operator-Name attribute content to use in the RADIUS config
423
     * 
424
     * @return string
425
     */
426
    public function getOperatorName()
427
    {
428
        $customAttrib = $this->getAttributes("managedsp:operatorname");
429
        if (count($customAttrib) == 0) {
430
            return "1sp." . $this->identifier . "-" . $this->institution . \config\ConfAssistant::SILVERBULLET['realm_suffix'];
431
        }
432
        return $customAttrib[0]["value"];
433
    }
434
435
    /**
436
     * send request to RADIUS configuration daemom
437
     *
438
     * @param  integer $idx  server index 1 (primary) or 2 (backup)
439
     * @param  string  $post string to POST 
440
     * @return string  OK or FAILURE
441
     */
442
    private function sendToRADIUS($idx, $post)
443
    {
444
445
        $hostname = "radius_hostname_$idx";
446
        $ch = curl_init("http://" . $this->$hostname);
447
        if ($ch === FALSE) {
448
            $res = 'FAILURE';
449
        } else {
450
            curl_setopt($ch, CURLOPT_POST, 1);
451
            curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
452
            $this->loggerInstance->debug(1, "Posting to http://" . $this->$hostname . ": $post\n");
453
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
454
            curl_setopt($ch, CURLOPT_HEADER, 0);
455
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
456
            $exec = curl_exec($ch);
457
            if (!is_string($exec)) {
458
                $this->loggerInstance->debug(1, "curl_exec failure");
459
                $res = 'FAILURE';
460
            } else {
461
                $res = $exec;
462
            }
463
            curl_close($ch);
464
            $this->loggerInstance->debug(1, "Response from FR configurator: $res\n");
465
            $this->loggerInstance->debug(1, $this);
466
        }
467
        $this->loggerInstance->debug(1, "Database update");
468
        $this->databaseHandle->exec("UPDATE deployment SET radius_status_$idx = " . ($res == 'OK' ? \core\AbstractDeployment::RADIUS_OK : \core\AbstractDeployment::RADIUS_FAILURE) . " WHERE deployment_id = $this->identifier");
469
        return $res;
470
    }
471
472
    /**
473
     * prepare and send email message to support mail
474
     *
475
     * @param  int    $remove   the flag indicating remove request
476
     * @param  array  $response setRADIUSconfig result
477
     * @param  string $status   the flag indicating status (FAILURE or OK)
478
     * @return void
479
     * 
480
     */
481
    private function sendMailtoAdmin($remove, $response, $status)
482
    {
483
        $txt = '';
484
        if ($status == 'OK') {
485
            $txt = $remove ? _('Profile deactivation succeeded') : _('Profile activation/modification succeeded');
486
        } else {
487
            $txt = $remove ? _('Profile deactivation failed') : _('Profile activation/modification failed');
488
        }
489
        $txt = $txt . ' ';
490
        if (array_count_values($response)[$status] == 2) {
491
            $txt = $txt . _('on both RADIUS servers: primary and backup') . '.';
492
        } else {
493
            if ($response['res[1]'] == $status) {
494
                $txt = $txt . _('on primary RADIUS server') . '.';
495
            } else {
496
                $txt = $txt . _('on backup RADIUS server') . '.';
497
            }
498
        }
499
        $mail = \core\common\OutsideComm::mailHandle();
500
        $email = $this->getAttributes("support:email")[0]['value'];
501
        $mail->FromName = \config\Master::APPEARANCE['productname'] . " Notification System";
502
        $mail->addAddress($email);
503
        if ($status == 'OK') {
504
            $mail->Subject = _('RADIUS profile update problem fixed');
505
        } else {
506
            $mail->Subject = _('RADIUS profile update problem');
507
        }
508
        $mail->Body = $txt;
509
        $sent = $mail->send();
510
        if ($sent === FALSE) {
511
            $this->loggerInstance->debug(1, 'Mailing on RADIUS problem failed');
512
        }
513
    }
514
515
    /**
516
     * check if URL responds with 200
517
     *
518
     * @param integer $idx server index 1 (primary) or 2 (backup)
519
     * @return integer or NULL
520
     */
521
    private function checkURL($idx)
522
    {
523
        $ch = curl_init();
524
        if ($ch === FALSE) {
525
            return NULL;
526
        }
527
        if ($idx == 1) {
528
            $host = $this->radius_hostname_1;
529
        } elseif ($idx == 2) {
530
            $host = $this->radius_hostname_2;
531
        } else {
532
            return NULL;
533
        }
534
        $timeout = 10;
535
        curl_setopt($ch, CURLOPT_URL, 'http://' . $host);
536
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
537
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
538
        curl_exec($ch);
539
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
540
        curl_close($ch);
541
        if ($http_code == 200) {
542
            return 1;
543
        }
544
        return 0;
545
    }
546
547
    /**
548
     * check whether the configured RADIUS hosts actually exist
549
     * 
550
     * @param integer $idx server index 1 (primary) or 2 (backup)
551
     * @return integer or NULL
552
     */
553
    private function testRADIUSHost($idx)
554
    {
555
        if ($idx == 1) {
556
            $host = $this->radius_hostname_1;
557
        } elseif ($idx == 2) {
558
            $host = $this->radius_hostname_2;
559
        } else {
560
            return NULL;
561
        }
562
        $statusServer = new diag\RFC5997Tests($host, \config\Diagnostics::RADIUSSPTEST['port'], \config\Diagnostics::RADIUSSPTEST['secret']);
563
        $this->loggerInstance->debug(1, $statusServer);
564
        if ($statusServer->statusServerCheck() === diag\AbstractTest::RETVAL_OK) {
565
            return 1;
566
        }
567
        return 0;
568
    }
569
570
    /**
571
     * get institution realms
572
     * 
573
     * @return array of strings
574
     */
575
    private function getAllRealms()
576
    {
577
        $idp = new IdP($this->institution);
578
        $allProfiles = $idp->listProfiles(TRUE);
579
        $allRealms = [];
580
        if (($this->getAttributes("managedsp:realmforvlan") ?? NULL)) {
581
            $allRealms = array_values(array_unique(array_column($this->getAttributes("managedsp:realmforvlan"), "value")));
582
        }
583
        foreach ($allProfiles as $profile) {
584
            if ($realm = ($profile->getAttributes("internal:realm")[0]['value'] ?? NULL)) {
585
                if (!in_array($realm, $allRealms)) {
586
                    $allRealms[] = $realm;
587
                }
588
            }
589
        }
590
        return $allRealms;
591
    }
592
593
    /**
594
     * check if RADIUS configuration deamon is listening for requests
595
     *
596
     * @return array index res[1] indicate primary RADIUS status, index res[2] backup RADIUS status
597
     */
598
    public function checkRADIUSHostandConfigDaemon()
599
    {
600
        $res = array();
601
        if ($this->radius_status_1 == \core\AbstractDeployment::RADIUS_FAILURE) {
602
            $res[1] = $this->checkURL(1);
603
            if ($res[1]) {
604
                $res[1] = $this->testRADIUSHost(1);
605
            }
606
        }
607
        if ($this->radius_status_2 == \core\AbstractDeployment::RADIUS_FAILURE) {
608
            $res[2] = $this->checkURL(2);
609
            if ($res[2]) {
610
                $res[2] = $this->testRADIUSHost(2);
611
            }
612
        }
613
        return $res;
614
    }
615
616
    /**
617
     * prepare request to add/modify RADIUS settings for given deployment
618
     *
619
     * @param int $onlyone the flag indicating on which server to conduct modifications
620
     * @param int $notify  the flag indicating that an email notification should be sent
621
     * @return array index res[1] indicate primary RADIUS status, index res[2] backup RADIUS status
622
     */
623
    public function setRADIUSconfig($onlyone = 0, $notify = 0)
624
    {
625
        $remove = ($this->status == \core\AbstractDeployment::INACTIVE) ? 0 : 1;
626
        $toPost = ($onlyone ? array($onlyone => '') : array(1 => '', 2 => ''));
627
        $toPostTemplate = 'instid=' . $this->institution . '&deploymentid=' . $this->identifier . '&secret=' . $this->secret . '&country=' . $this->getAttributes("internal:country")[0]['value'] . '&';
628
        if ($remove) {
629
            $toPostTemplate = $toPostTemplate . 'remove=1&';
630
        } else {
631
            $toPostTemplate = $toPostTemplate . 'operatorname=' . $this->getOperatorName() . '&'; 
632
            if ($this->getAttributes("managedsp:vlan")[0]['value'] ?? NULL) {
633
                $allRealms = $this->getAllRealms();
634
                if (!empty($allRealms)) {
635
                    $toPostTemplate = $toPostTemplate . 'vlan=' . $this->getAttributes("managedsp:vlan")[0]['value'] . '&';
636
                    $toPostTemplate = $toPostTemplate . 'realmforvlan[]=' . implode('&realmforvlan[]=', $allRealms) . '&';
637
                }
638
            }
639
        }
640
        foreach (array_keys($toPost) as $key) {
641
            $elem = 'port' . $key;
642
            $toPost[$key] = $toPostTemplate . 'port=' . $this->$elem;
643
        }
644
        $response = array();
645
        foreach ($toPost as $key => $value) {
646
            $this->loggerInstance->debug(1, 'toPost ' . $toPost[$key] . "\n");
647
            $response['res[' . $key . ']'] = $this->sendToRADIUS($key, $toPost[$key]);
648
        }
649
        if ($onlyone) {
650
            $response['res[' . ($onlyone == 1) ? 2 : 1 . ']'] = \core\AbstractDeployment::RADIUS_OK;
651
        }
652
        foreach (array('OK', 'FAILURE') as $status) {
653
            if ( ( ($status == 'OK' && $notify) || ($status == 'FAILURE') ) && ( in_array($status, $response) ) ) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($status == 'OK' && $not...ray($status, $response), Probably Intended Meaning: $status == 'OK' && $noti...ay($status, $response))
Loading history...
654
                $this->sendMailtoAdmin($remove, $response, $status);
655
            }
656
        }
657
        return $response;
658
    }
659
}
660