Test Failed
Push — master ( 47551f...fb8586 )
by Maja
11:24
created

DeploymentManaged::renewtls()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 5
rs 10
cc 1
nc 1
nop 0
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
 * @author Maja Górecka-Wolniewicz <[email protected]>
30
 *
31
 * @package Developer
32
 *
33
 */
34
35
namespace core;
36
37
use \Exception;
0 ignored issues
show
Bug introduced by
The type \Exception 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...
38
39
/**
40
 * This class represents an EAP Profile.
41
 * Profiles can inherit attributes from their IdP, if the IdP has some. Otherwise,
42
 * one can set attribute in the Profile directly. If there is a conflict between
43
 * IdP-wide and Profile-wide attributes, the more specific ones (i.e. Profile) win.
44
 * 
45
 * @author Stefan Winter <[email protected]>
46
 * @author Tomasz Wolniewicz <[email protected]>
47
 *
48
 * @license see LICENSE file in root directory
49
 *
50
 * @package Developer
51
 */
52
class DeploymentManaged extends AbstractDeployment
53
{
54
55
    /**
56
     * This is the limit for dual-stack hosts. Single stack uses half of the FDs
57
     * in FreeRADIUS and take twice as many. initialise() takes this into
58
     * account.
59
     */
60
    const MAX_CLIENTS_PER_SERVER = 200;
61
    const PRODUCTNAME = "Managed SP";
62
63
    /**
64
     * the primary RADIUS server port for this SP instance
65
     * 
66
     * @var integer
67
     */
68
    public $port1;
69
70
    /**
71
     * the backup RADIUS server port for this SP instance
72
     * 
73
     * @var integer
74
     */
75
    public $port2;
76
77
    /**
78
     * the shared secret for this SP instance
79
     * 
80
     * @var string
81
     */
82
    public $secret;
83
84
    /**
85
     * the IPv4 address of the primary RADIUS server for this SP instance 
86
     * (can be NULL)
87
     * 
88
     * @var string
89
     */
90
    public $host1_v4;
91
92
    /**
93
     * the IPv6 address of the primary RADIUS server for this SP instance 
94
     * (can be NULL)
95
     * 
96
     * @var string
97
     */
98
    public $host1_v6;
99
100
    /**
101
     * the IPv4 address of the backup RADIUS server for this SP instance 
102
     * (can be NULL)
103
     * 
104
     * @var string
105
     */
106
    public $host2_v4;
107
108
    /**
109
     * the IPv6 address of the backup RADIUS server for this SP instance 
110
     * (can be NULL)
111
     * 
112
     * @var string
113
     */
114
    public $host2_v6;
115
116
    /**
117
     * the primary RADIUS server instance for this SP instance
118
     * 
119
     * @var string
120
     */
121
    public $radius_instance_1;
122
123
    /**
124
     * the backup RADIUS server instance for this SP instance
125
     * 
126
     * @var string
127
     */
128
    public $radius_instance_2;
129
130
    /**
131
     * the primary RADIUS server hostname - for sending configuration requests
132
     * 
133
     * @var string
134
     */
135
    public $radius_hostname_1;
136
137
    /**
138
     * the backup RADIUS server hostname - for sending configuration requests
139
     * 
140
     * @var string
141
     */
142
    public $radius_hostname_2;
143
144
    /**
145
     * the primary RADIUS server status - last configuration request result
146
     * 
147
     * @var string
148
     */
149
    public $radius_status_1;
150
151
    /**
152
     * the backup RADIUS server status - last configuration request result
153
     * 
154
     * @var string
155
     */
156
    public $radius_status_2;
157
    
158
    /**
159
     * the client private key
160
     * 
161
     * @var string
162
     */
163
    public $radsec_priv;
164
    
165
    /**
166
     * the client certificate
167
     * 
168
     * @var string
169
     */
170
    public $radsec_cert; 
171
    
172
    /**
173
     * the TLS-PSK key
174
     * 
175
     * @var string
176
     */
177
    public $pskkey;
178
    
179
    /**
180
     * the consortium this deployment is attached to
181
     * 
182
     * @var string
183
     */
184
    public $consortium;
185
186
    /**
187
     * the T&C of this service
188
     */
189
    public $termsAndConditions;
190
    
191
    /**
192
     * Class constructor for existing deployments (use 
193
     * IdP::newDeployment() to actually create one). Retrieves all 
194
     * attributes from the DB and stores them in the priv_ arrays.
195
     * 
196
     * @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.
197
     * @param string|int $deploymentIdRaw identifier of the deployment in the DB
198
     * @param string     $consortium      identifier of the consortium to attach to (only relevant when initialising a new deployment for the first time
199
     * @throws Exception
200
     */
201
    public function __construct($idpObject, $deploymentIdRaw, $consortium = 'eduroam')
202
    {
203
        parent::__construct($idpObject, $deploymentIdRaw); // we now have access to our INST database handle and logging
204
        $this->entityOptionTable = "deployment_option";
205
        $this->entityIdColumn = "deployment_id";
206
        $this->type = AbstractDeployment::DEPLOYMENTTYPE_MANAGED;
207
        if (!is_numeric($deploymentIdRaw)) {
208
            throw new Exception("Managed SP instances have to have a numeric identifier");
209
        }
210
        $propertyQuery = "SELECT consortium,status,port_instance_1,port_instance_2,secret,radius_instance_1,radius_instance_2,radius_status_1,radius_status_2,radsec_priv,radsec_cert,pskkey FROM deployment WHERE deployment_id = ?";
211
        $queryExec = $this->databaseHandle->exec($propertyQuery, "i", $deploymentIdRaw);
212
        if (mysqli_num_rows(/** @scrutinizer ignore-type */ $queryExec) == 0) {
213
            throw new Exception("Attempt to construct an unknown DeploymentManaged!");
214
        }
215
        
216
        $this->identifier = $deploymentIdRaw;
217
        while ($iterator = mysqli_fetch_object(/** @scrutinizer ignore-type */ $queryExec)) {
218
            if ($iterator->secret == NULL && $iterator->radius_instance_1 == NULL) {
219
                // we are instantiated for the first time, or all previous init attempts failed - so initialise us
220
                // first time: note the consortium, permanently
221
                if ($iterator->consortium === NULL) {
222
                    $this->consortium = $consortium;
223
                    $consortiumQuery = "UPDATE deployment SET consortium = '$this->consortium' WHERE deployment_id = ?";
224
                    $this->databaseHandle->exec($consortiumQuery, "i", $deploymentIdRaw);
225
                }
226
                $details = $this->initialise();
227
                $this->port1 = $details["port_instance_1"];
228
                $this->port2 = $details["port_instance_2"];
229
                $this->secret = $details["secret"];
230
                $this->radius_instance_1 = $details["radius_instance_1"];
231
                $this->radius_instance_2 = $details["radius_instance_2"];
232
                $this->radius_status_1 = 1;
233
                $this->radius_status_2 = 1;
234
                $this->status = AbstractDeployment::INACTIVE;
235
            } else {
236
                $this->port1 = $iterator->port_instance_1;
237
                $this->port2 = $iterator->port_instance_2;
238
                $this->secret = $iterator->secret;
239
                $this->radius_instance_1 = $iterator->radius_instance_1;
240
                $this->radius_instance_2 = $iterator->radius_instance_2;
241
                $this->radius_status_1 = $iterator->radius_status_1;
242
                $this->radius_status_2 = $iterator->radius_status_2;
243
                $this->status = $iterator->status;
244
                $this->consortium = $iterator->consortium;
245
                $this->radsec_cert = $iterator->radsec_cert;
246
                $this->radsec_priv = $iterator->radsec_priv;
247
                $this->pskkey = $iterator->pskkey;
248
            }
249
        }
250
        $server1 = $this->radius_instance_1;
251
        
252
        $server1details = $this->databaseHandle->exec("SELECT mgmt_hostname, radius_ip4, radius_ip6 FROM managed_sp_servers WHERE server_id = ?", "s", $server1);
253
        while ($iterator2 = mysqli_fetch_object(/** @scrutinizer ignore-type */ $server1details)) {
254
            $this->host1_v4 = $iterator2->radius_ip4;
255
            $this->host1_v6 = $iterator2->radius_ip6;
256
            $this->radius_hostname_1 = $iterator2->mgmt_hostname;
257
        }
258
        $server2 = $this->radius_instance_2;
259
        $server2details = $this->databaseHandle->exec("SELECT mgmt_hostname, radius_ip4, radius_ip6 FROM managed_sp_servers WHERE server_id = ?", "s", $server2);
260
        while ($iterator3 = mysqli_fetch_object(/** @scrutinizer ignore-type */ $server2details)) {
261
            $this->host2_v4 = $iterator3->radius_ip4;
262
            $this->host2_v6 = $iterator3->radius_ip6;
263
            $this->radius_hostname_2 = $iterator3->mgmt_hostname;
264
        }
265
        $thisLevelAttributes = $this->retrieveOptionsFromDatabase("SELECT DISTINCT option_name, option_lang, option_value, row_id 
266
                                            FROM $this->entityOptionTable
267
                                            WHERE $this->entityIdColumn = ?  
268
                                            ORDER BY option_name", "Profile");
269
        $tempAttribMergedIdP = $this->levelPrecedenceAttributeJoin($thisLevelAttributes, $this->idpAttributes, "IdP");
270
        $this->attributes = $this->levelPrecedenceAttributeJoin($tempAttribMergedIdP, $this->fedAttributes, "FED");
271
        
272
        $this->termsAndConditions = "<h2>Product Definition</h2>
273
<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>
274
<p>The service includes:</p>
275
<ul><li>web-based user management interface where eduroam SP deployment details can be created and deleted;</li>
276
    <li>web-based institution management interface where institutions are enabled or disabled to use the service;</li>
277
    <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>
278
</ul>
279
<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>
280
<h2>Terms of Use</h2>
281
<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).";
282
    }
283
284
    /**
285
     * finds a suitable server which is geographically close to the admin
286
     * 
287
     * @param array  $adminLocation      the current geographic position of the admin
288
     * @param string $federation         the federation this deployment belongs to
289
     * @param array  $blacklistedServers list of server to IGNORE
290
     * @return string the server ID
291
     * @throws Exception
292
     */
293
    private function findGoodServerLocation($adminLocation, $federation, $blacklistedServers)
294
    {
295
        // find a server near him (list of all servers with capacity, ordered by distance)
296
        // first, if there is a pool of servers specifically for this federation, prefer it
297
        // only check the consortium pool group we want to attach to
298
        $cons = $this->consortium;
299
        $servers = $this->databaseHandle->exec("SELECT server_id, radius_ip4, radius_ip6, location_lon, location_lat FROM managed_sp_servers WHERE pool = ? AND consortium = ?", "ss", $federation, $cons);
300
        $serverCandidates = [];
301
        while ($iterator = mysqli_fetch_object(/** @scrutinizer ignore-type */ $servers)) {
302
            $maxSupportedClients = DeploymentManaged::MAX_CLIENTS_PER_SERVER;
303
            if ($iterator->radius_ip4 == NULL || $iterator->radius_ip6 == NULL) {
304
                // half the amount of IP stacks means half the amount of FDs in use, so we can take twice as many
305
                $maxSupportedClients = $maxSupportedClients * 2;
306
            }
307
            $serverId = $iterator->server_id;
308
            $clientCount1 = $this->databaseHandle->exec("SELECT port_instance_1 AS tenants1 FROM deployment WHERE radius_instance_1 = ?", "s", $serverId);
309
            $clientCount2 = $this->databaseHandle->exec("SELECT port_instance_2 AS tenants2 FROM deployment WHERE radius_instance_2 = ?", "s", $serverId);
310
311
            $clients = $clientCount1->num_rows + $clientCount2->num_rows;
312
            if (in_array($iterator->server_id, $blacklistedServers)) {
313
                continue;
314
            }
315
            if ($clients < $maxSupportedClients) {
316
                $serverCandidates[IdPlist::geoDistance($adminLocation, ['lat' => $iterator->location_lat, 'lon' => $iterator->location_lon])] = $iterator->server_id;
317
            }
318
            if ($clients > $maxSupportedClients * 0.9) {
319
                $this->loggerInstance->debug(1, "A RADIUS server for Managed SP (" . $iterator->server_id . ") is serving at more than 90% capacity!");
320
            }
321
        }
322
        if (count($serverCandidates) == 0 && $federation != "DEFAULT") {
323
            // we look in the default pool instead
324
            // recursivity! Isn't that cool!
325
            return $this->findGoodServerLocation($adminLocation, "DEFAULT", $blacklistedServers);
326
        }
327
        if (count($serverCandidates) == 0) {
328
            throw new Exception("No available server found for new SP in $federation!");
329
        }
330
        // put the nearest server on top of the list
331
        ksort($serverCandidates);
332
        $this->loggerInstance->debug(1, $serverCandidates);
333
        return array_shift($serverCandidates);
334
    }
335
336
    /**
337
     * create TLS credentials for client
338
     * 
339
     * @throws Exception
340
     */
341
    private function createTLScredentials()
342
    {
343
        $clientName = "SP_" . $this->identifier . '-' . $this->institution;
344
        $dn = array(
345
                    "organizationName" => "eduroam",
346
                    "organizationalUnitName" => "eduroam Managed SP",
347
                    "commonName" => $clientName
348
        );
349
        // Generate a new private (and public) key pair
350
        $privkey = openssl_pkey_new(array(
351
                                          "private_key_bits" => 4096,
352
                                          "private_key_type" => OPENSSL_KEYTYPE_RSA));
353
        // export private key to $clientprivateKey (as string)
354
        openssl_pkey_export($privkey, $this->radsec_priv);
355
        // Generate a certificate signing request
356
        $csr = openssl_csr_new($dn, $privkey,
0 ignored issues
show
Bug introduced by
It seems like $privkey can also be of type resource; however, parameter $private_key of openssl_csr_new() does only seem to accept OpenSSLAsymmetricKey, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

356
        $csr = openssl_csr_new($dn, /** @scrutinizer ignore-type */ $privkey,
Loading history...
357
                               array('digest_alg' => 'sha256', 'config' => ROOT . "/config/ManagedSPCerts/openssl.cnf"));
358
        // get CA certificate and private key
359
        $caprivkey = array(file_get_contents(ROOT . "/config/ManagedSPCerts/eduroamSP-CA.key"),
360
                            \config\Master::MANAGEDSP['capass']);
0 ignored issues
show
Bug introduced by
The constant config\Master::MANAGEDSP was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
361
        $cacert = file_get_contents(ROOT .  "/config/ManagedSPCerts/eduroamSP-CA.pem");
362
        $clientcert = openssl_csr_sign($csr, $cacert, $caprivkey, \config\Master::MANAGEDSP['daystoexpiry'],
363
                          array('digest_alg'=>'sha256', 'config' => ROOT . "/config/ManagedSPCerts/openssl.cnf"), rand());
364
        openssl_x509_export($clientcert, $this->radsec_cert);
365
    } 
366
    /**
367
     * retrieves usage statistics for the deployment
368
     * 
369
     * @param int $backlog how many seconds back in time should we look
370
     * @param int $limit   max number of entries to retrieve (if supplied, backlog will be ignored)
371
     * @return array of arrays: activity_time, realm, mac, result
372
     * @throws Exception
373
     */
374
    public function retrieveStatistics(int $backlog, int $limit = 0)
375
    {
376
        // find a server near him (list of all servers with capacity, ordered by distance)
377
        // first, if there is a pool of servers specifically for this federation, prefer it
378
        // only check the consortium pool group we want to attach to
379
        // TODO: if we also collect stats from OpenRoaming hosts, differentiate the logs!
380
        $opName = $this->getOperatorName();
381
        if ($limit !== 0) {
382
            $conditional1 = "";
383
            $conditional2 = "DESC LIMIT $limit";
384
        } else {
385
            $conditional1 = "AND activity_time > DATE_SUB(NOW(), INTERVAL $backlog SECOND)";
386
            $conditional2 = "";
387
        }
388
        $stats = $this->databaseHandle->exec("SELECT activity_time, realm, mac, cui, result, ap_id FROM activity WHERE operatorname = ? $conditional1 ORDER BY activity_time $conditional2", "s", $opName );
389
        return mysqli_fetch_all($stats, \MYSQLI_ASSOC);
390
    }
391
        
392
    /**
393
     * initialises a new SP
394
     * 
395
     * @return array details of the SP as generated during initialisation
396
     * @throws Exception
397
     */
398
    private function initialise()
399
    {
400
        // find out where the admin is located approximately
401
        $ourLocation = ['lon' => 0, 'lat' => 0];
402
        $geoip = DeviceLocation::locateDevice();
403
        if ($geoip['status'] == 'ok') {
404
            $ourLocation = ['lon' => $geoip['geo']['lon'], 'lat' => $geoip['geo']['lat']];
405
        }
406
        $inst = new IdP($this->institution);
407
        $ourserver = $this->findGoodServerLocation($ourLocation, $inst->federation, []);
408
        // now, find an unused port in the preferred server
409
        $foundFreePort1 = 0;
410
        while ($foundFreePort1 == 0) {
411
            $portCandidate = random_int(1200, 65535);
412
            $check = $this->databaseHandle->exec("SELECT port_instance_1 FROM deployment WHERE radius_instance_1 = ? AND port_instance_1 = ?", "si", $ourserver, $portCandidate);
413
            if (mysqli_num_rows(/** @scrutinizer ignore-type */ $check) == 0) {
414
                $foundFreePort1 = $portCandidate;
415
            }
416
        }
417
        $ourSecondServer = $this->findGoodServerLocation($ourLocation, $inst->federation, [$ourserver]);
418
        $foundFreePort2 = 0;
419
        while ($foundFreePort2 == 0) {
420
            $portCandidate = random_int(1200, 65535);
421
            $check = $this->databaseHandle->exec("SELECT port_instance_2 FROM deployment WHERE radius_instance_2 = ? AND port_instance_2 = ?", "si", $ourSecondServer, $portCandidate);
422
            if (mysqli_num_rows(/** @scrutinizer ignore-type */ $check) == 0) {
423
                $foundFreePort2 = $portCandidate;
424
            }
425
        }
426
        // and make up a shared secret that is halfways readable
427
        $futureSecret = trim(chunk_split(bin2hex(openssl_random_pseudo_bytes(14)), 4, '-'), '-');
428
        $this->createTLScredentials();
429
        $futurePSKkey = bin2hex(openssl_random_pseudo_bytes(32));
430
        $cons = $this->consortium;
431
        $id = $this->identifier;
432
        
433
        $this->databaseHandle->exec("UPDATE deployment SET radius_instance_1 = ?, radius_instance_2 = ?, port_instance_1 = ?, port_instance_2 = ?, secret = ?, radsec_priv = ?, radsec_cert = ?, pskkey = ?, consortium = ? WHERE deployment_id = ?", "ssiisssssi", $ourserver, $ourSecondServer, $foundFreePort1, $foundFreePort2, $futureSecret, $this->radsec_priv, $this->radsec_cert, $futurePSKkey, $cons, $id);
434
        return ["port_instance_1" => $foundFreePort1, "port_instance_2" => $foundFreePort2, "secret" => $futureSecret, "radius_instance_1" => $ourserver, "radius_instance_2" => $ourSecondServer, "pskkey" => $futurePSKkey];
435
    }
436
437
    /**
438
     * update the last_changed timestamp for this deployment
439
     * 
440
     * @return void
441
     */
442
    public function updateFreshness()
443
    {
444
        $id = $this->identifier;
445
        $this->databaseHandle->exec("UPDATE deployment SET last_change = CURRENT_TIMESTAMP WHERE deployment_id = ?", "i", $id);
446
    }
447
448
    /**
449
     * gets the last-modified timestamp (useful for caching "dirty" check)
450
     * 
451
     * @return string the date in string form, as returned by SQL
452
     */
453
    public function getFreshness()
454
    {
455
        $id = $this->identifier;
456
        $execLastChange = $this->databaseHandle->exec("SELECT last_change FROM deployment WHERE deployment_id = ?", "i", $id);
457
        // SELECT always returns a resource, never a boolean
458
        if ($freshnessQuery = mysqli_fetch_object(/** @scrutinizer ignore-type */ $execLastChange)) {
459
            return $freshnessQuery->last_change;
460
        }
461
    }
462
463
    /**
464
     * Deletes the deployment from database
465
     * 
466
     * @return void
467
     */
468
    public function remove()
469
    {
470
        $id = $this->identifier;
471
        $this->databaseHandle->exec("DELETE FROM deployment_option WHERE deployment_id = ?", "i", $id);
472
        $this->databaseHandle->exec("DELETE FROM deployment WHERE deployment_id = ?", "i", $id);
473
    }
474
    
475
    /**
476
     * Renews the deployment TLS credentials
477
     * 
478
     * @return void
479
     */
480
    public function renewtls()
481
    {
482
       $id = $this->identifier;
483
       $futureTlsClient = $this->createTLScredentials();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $futureTlsClient is correct as $this->createTLScredentials() targeting core\DeploymentManaged::createTLScredentials() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Unused Code introduced by
The assignment to $futureTlsClient is dead and can be removed.
Loading history...
484
       $this->databaseHandle->exec("UPDATE deployment SET radsec_priv = ?, radsec_cert = ? WHERE deployment_id = ?", "ssi", $this->radsec_priv, $this->radsec_cert, $id);           
485
    }
486
    /**
487
     * marks the deployment as deactivated 
488
     * 
489
     * @return void
490
     */
491
    public function deactivate()
492
    {
493
        $id = $this->identifier;
494
        $inactive = DeploymentManaged::INACTIVE;
495
        $this->databaseHandle->exec("UPDATE deployment SET status = ? WHERE deployment_id = ?", "ii", $inactive, $id);
496
    }
497
    
498
    /**
499
     * marks the deployment as active.
500
     * 
501
     * @return void
502
     */
503
    public function activate()
504
    {
505
        $id = $this->identifier;
506
        $active = DeploymentManaged::ACTIVE;
507
        $this->databaseHandle->exec("UPDATE deployment SET status = ? WHERE deployment_id = ?", "ii", $active, $id);
508
    }
509
510
    /**
511
     * determines the Operator-Name attribute content to use in the RADIUS config
512
     * 
513
     * @return string
514
     */
515
    public function getOperatorName()
516
    {
517
        $customAttrib = $this->getAttributes("managedsp:operatorname");
518
        if (count($customAttrib) == 0) {
519
            return "1sp." . $this->identifier . "-" . $this->institution . \config\ConfAssistant::SILVERBULLET['realm_suffix'];
520
        }
521
        return $customAttrib[0]["value"];
522
    }
523
524
    /**
525
     * send request to RADIUS configuration daemom
526
     *
527
     * @param  integer $idx  server index 1 (primary) or 2 (backup)
528
     * @param  string  $post string to POST 
529
     * @return string  OK or FAILURE
530
     */
531
    private function sendToRADIUS(int $idx, $post)
532
    {
533
        $hostname = "radius_hostname_$idx";
534
        $ch = curl_init("http://" . $this->$hostname . ':8080');
535
        if ($ch === FALSE) {
536
            $res = 'FAILURE';
537
        } else {
538
            curl_setopt($ch, CURLOPT_POST, 1);
539
            curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
540
            $this->loggerInstance->debug(1, "Posting to http://" . $this->$hostname . ":8080/$post\n");
541
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
542
            curl_setopt($ch, CURLOPT_HEADER, 0);
543
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
544
            $exec = curl_exec($ch);
545
            if (!is_string($exec)) {
546
                $this->loggerInstance->debug(1, "curl_exec failure");
547
                $res = 'FAILURE';
548
            } else {
549
                $res = $exec;
550
            }
551
            curl_close($ch);
552
            $this->loggerInstance->debug(1, "Response from FR configurator: $res\n");
553
            $this->loggerInstance->debug(1, $this);
554
        }
555
        $this->loggerInstance->debug(1, "Database update");
556
        $id = $this->identifier;
557
        $resValue = ($res == 'OK' ? \core\AbstractDeployment::RADIUS_OK : \core\AbstractDeployment::RADIUS_FAILURE);
558
        $this->databaseHandle->exec("UPDATE deployment SET radius_status_$idx = ? WHERE deployment_id = ?", "ii", $resValue, $id);
559
        return $res;
560
    }
561
562
    /**
563
     * prepare and send email message to support mail
564
     *
565
     * @param  int    $remove   the flag indicating remove request
566
     * @param  array  $response setRADIUSconfig result
567
     * @param  string $status   the flag indicating status (FAILURE or OK)
568
     * @return void
569
     * 
570
     */
571
    private function sendMailtoAdmin($remove, $response, $status)
572
    {
573
        $txt = '';
574
        if ($status == 'OK') {
575
            $txt = $remove ? _('Profile deactivation succeeded') : _('Profile activation/modification succeeded');
576
        } else {
577
            $txt = $remove ? _('Profile deactivation failed') : _('Profile activation/modification failed');
578
        }
579
        $txt = $txt . ' ';
580
        if (array_count_values($response)[$status] == 2) {
581
            $txt = $txt . _('on both RADIUS servers: primary and backup') . '.';
582
        } else {
583
            if ($response['res[1]'] == $status) {
584
                $txt = $txt . _('on primary RADIUS server') . '.';
585
            } else {
586
                $txt = $txt . _('on backup RADIUS server') . '.';
587
            }
588
        }
589
        $mail = \core\common\OutsideComm::mailHandle();
590
        $email = $this->getAttributes("support:email")[0]['value'];
591
        $mail->FromName = \config\Master::APPEARANCE['productname'] . " Notification System";
592
        $mail->addAddress($email);
593
        if ($status == 'OK') {
594
            $mail->Subject = _('RADIUS profile update problem fixed');
595
        } else {
596
            $mail->Subject = _('RADIUS profile update problem');
597
        }
598
        $mail->Body = $txt;
599
        $sent = $mail->send();
600
        if ($sent === FALSE) {
601
            $this->loggerInstance->debug(1, 'Mailing on RADIUS problem failed');
602
        }
603
    }
604
605
    /**
606
     * check if URL responds with 200
607
     *
608
     * @param integer $idx server index 1 (primary) or 2 (backup)
609
     * @return integer or NULL
610
     */
611
    private function checkURL($idx)
612
    {
613
        $ch = curl_init();
614
        if ($ch === FALSE) {
615
            return NULL;
616
        }
617
        if ($idx == 1) {
618
            $host = $this->radius_hostname_1;
619
        } elseif ($idx == 2) {
620
            $host = $this->radius_hostname_2;
621
        } else {
622
            return NULL;
623
        }
624
        $timeout = 10;
625
        curl_setopt($ch, CURLOPT_URL, 'http://' . $host);
626
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
627
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
628
        curl_exec($ch);
629
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
630
        curl_close($ch);
631
        if ($http_code == 200) {
632
            return 1;
633
        }
634
        return 0;
635
    }
636
637
    /**
638
     * check whether the configured RADIUS hosts actually exist
639
     * 
640
     * @param integer $idx server index 1 (primary) or 2 (backup)
641
     * @return integer or NULL
642
     */
643
    private function testRADIUSHost($idx)
644
    {
645
        if ($idx == 1) {
646
            $host = $this->radius_hostname_1;
647
        } elseif ($idx == 2) {
648
            $host = $this->radius_hostname_2;
649
        } else {
650
            return NULL;
651
        }
652
        $statusServer = new diag\RFC5997Tests($host, \config\Diagnostics::RADIUSSPTEST['port'], \config\Diagnostics::RADIUSSPTEST['secret']);
653
        $this->loggerInstance->debug(1, $statusServer);
654
        if ($statusServer->statusServerCheck() === diag\AbstractTest::RETVAL_OK) {
655
            return 1;
656
        }
657
        return 0;
658
    }
659
660
    /**
661
     * get institution realms
662
     * 
663
     * @return array of strings
664
     */
665
    private function getAllRealms()
666
    {
667
        $idp = new IdP($this->institution);
668
        $allProfiles = $idp->listProfiles(TRUE);
669
        $allRealms = [];
670
        if (($this->getAttributes("managedsp:realmforvlan") ?? NULL)) {
671
            $allRealms = array_values(array_unique(array_column($this->getAttributes("managedsp:realmforvlan"), "value")));
672
        }
673
        foreach ($allProfiles as $profile) {
674
            if ($realm = ($profile->getAttributes("internal:realm")[0]['value'] ?? NULL)) {
675
                if (!in_array($realm, $allRealms)) {
676
                    $allRealms[] = $realm;
677
                }
678
            }
679
        }
680
        return $allRealms;
681
    }
682
683
    /**
684
     * check if RADIUS configuration daemon is listening for requests
685
     *
686
     * @return array index res[1] indicate primary RADIUS status, index res[2] backup RADIUS status
687
     */
688
    public function checkRADIUSHostandConfigDaemon()
689
    {
690
        $res = array();
691
        if ($this->radius_status_1 == \core\AbstractDeployment::RADIUS_FAILURE) {
692
            $res[1] = $this->checkURL(1);
693
            if ($res[1]) {
694
                $res[1] = $this->testRADIUSHost(1);
695
            }
696
        }
697
        if ($this->radius_status_2 == \core\AbstractDeployment::RADIUS_FAILURE) {
698
            $res[2] = $this->checkURL(2);
699
            if ($res[2]) {
700
                $res[2] = $this->testRADIUSHost(2);
701
            }
702
        }
703
        return $res;
704
    }
705
706
    /**
707
     * prepare request to add/modify RADIUS settings for given deployment
708
     *
709
     * @param int $onlyone the flag indicating on which server to conduct modifications
710
     * @param int $notify  the flag indicating that an email notification should be sent
711
     * @return array index res[1] indicate primary RADIUS status, index res[2] backup RADIUS status
712
     */
713
    public function setRADIUSconfig($onlyone = 0, $notify = 0)
714
    {
715
        $remove = ($this->status == \core\AbstractDeployment::INACTIVE) ? 0 : 1;
716
        $toPost = ($onlyone ? array($onlyone => '') : array(1 => '', 2 => ''));
717
        $toPostTemplate = 'instid=' . $this->institution . '&deploymentid=' . $this->identifier . 
718
                '&secret=' . $this->secret .
719
                '&country=' . $this->getAttributes("internal:country")[0]['value'] .
720
                '&pskkey=' . $this->pskkey . '&';
721
        if ($remove) {
722
            $toPostTemplate = $toPostTemplate . 'remove=1&';
723
        } else {
724
            $toPostTemplate = $toPostTemplate . 'operatorname=' . $this->getOperatorName() . '&'; 
725
            if ($this->getAttributes("managedsp:vlan")[0]['value'] ?? NULL) {
726
                $allRealms = $this->getAllRealms();
727
                if (!empty($allRealms)) {
728
                    $toPostTemplate = $toPostTemplate . 'vlan=' . $this->getAttributes("managedsp:vlan")[0]['value'] . '&';
729
                    $toPostTemplate = $toPostTemplate . 'realmforvlan[]=' . implode('&realmforvlan[]=', $allRealms) . '&';
730
                }
731
            }
732
        }
733
        foreach (array_keys($toPost) as $key) {
734
            $elem = 'port' . $key;
735
            $toPost[$key] = $toPostTemplate . 'port=' . $this->$elem;
736
        }
737
        $response = array();
738
        foreach ($toPost as $key => $value) {
739
            $this->loggerInstance->debug(1, 'toPost ' . $toPost[$key] . "\n");
740
            // temporarly one server $response['res[' . $key . ']'] = $this->sendToRADIUS($key, $toPost[$key]);
741
            if ($key == 2) {
742
                $response['res[2]'] = 'OK'; 
743
            } else {
744
                $response['res[' . $key . ']'] = $this->sendToRADIUS($key, $toPost[$key]);
745
            }
746
        }
747
        if ($onlyone) {
748
            $response['res[' . ($onlyone == 1) ? 2 : 1 . ']'] = \core\AbstractDeployment::RADIUS_OK;
749
        }
750
        foreach (array('OK', 'FAILURE') as $status) {
751
            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...
752
                $this->sendMailtoAdmin($remove, $response, $status);
753
            }
754
        }
755
        return $response;
756
    }
757
}
758