Passed
Push — master ( 3d7ab7...c6cd3b )
by Maja
07:26
created

DeploymentManaged::deactivate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 2
c 0
b 0
f 0
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
 *
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
     * This is the limit for dual-stack hosts. Single stack uses half of the FDs
55
     * in FreeRADIUS and take twice as many. initialise() takes this into
56
     * account.
57
     */
58
    const MAX_CLIENTS_PER_SERVER = 200;
59
    const PRODUCTNAME = "Managed SP";
60
61
    /**
62
     * the primary RADIUS server port for this SP instance
63
     * 
64
     * @var integer
65
     */
66
    public $port1;
67
68
    /**
69
     * the backup RADIUS server port for this SP instance
70
     * 
71
     * @var integer
72
     */
73
    public $port2;
74
75
    /**
76
     * the shared secret for this SP instance
77
     * 
78
     * @var string
79
     */
80
    public $secret;
81
82
    /**
83
     * the IPv4 address of the primary RADIUS server for this SP instance 
84
     * (can be NULL)
85
     * 
86
     * @var string
87
     */
88
    public $host1_v4;
89
90
    /**
91
     * the IPv6 address of the primary RADIUS server for this SP instance 
92
     * (can be NULL)
93
     * 
94
     * @var string
95
     */
96
    public $host1_v6;
97
98
    /**
99
     * the IPv4 address of the backup RADIUS server for this SP instance 
100
     * (can be NULL)
101
     * 
102
     * @var string
103
     */
104
    public $host2_v4;
105
106
    /**
107
     * the IPv6 address of the backup RADIUS server for this SP instance 
108
     * (can be NULL)
109
     * 
110
     * @var string
111
     */
112
    public $host2_v6;
113
114
    /**
115
     * the primary RADIUS server instance for this SP instance
116
     * 
117
     * @var string
118
     */
119
    public $radius_instance_1;
120
121
    /**
122
     * the backup RADIUS server instance for this SP instance
123
     * 
124
     * @var string
125
     */
126
    public $radius_instance_2;
127
    
128
    /**
129
     * the primary RADIUS server hostname - for sending configuration requests
130
     * 
131
     * @var string
132
     */
133
    public $radius_hostname_1;
134
135
    /**
136
     * the backup RADIUS server hostname - for sending configuration requests
137
     * 
138
     * @var string
139
     */
140
    public $radius_hostname_2;
141
142
    /**
143
     * the primary RADIUS server status - last configuration request result
144
     * 
145
     * @var string
146
     */
147
    public $radius_status_1;
148
149
    /**
150
     * the backup RADIUS server status - last configuration request result
151
     * 
152
     * @var string
153
     */
154
    public $radius_status_2;
155
    /**
156
     * Class constructor for existing deployments (use 
157
     * IdP::newDeployment() to actually create one). Retrieves all 
158
     * attributes from the DB and stores them in the priv_ arrays.
159
     * 
160
     * @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.
161
     * @param string|int $deploymentIdRaw identifier of the deployment in the DB
162
     * @throws Exception
163
     */
164
    public function __construct($idpObject, $deploymentIdRaw) {
165
        parent::__construct($idpObject, $deploymentIdRaw); // we now have access to our INST database handle and logging
166
        $this->entityOptionTable = "deployment_option";
167
        $this->entityIdColumn = "deployment_id";
168
        $this->type = AbstractDeployment::DEPLOYMENTTYPE_MANAGED;
169
        if (!is_numeric($deploymentIdRaw)) {
170
            throw new Exception("Managed SP instances have to have a numeric identifier");
171
        }
172
        $propertyQuery = "SELECT status,port_instance_1,port_instance_2,secret,radius_instance_1,radius_instance_2,radius_status_1,radius_status_2 FROM deployment WHERE deployment_id = ?";
173
        $queryExec = $this->databaseHandle->exec($propertyQuery, "i", $deploymentIdRaw);
174
        if (mysqli_num_rows(/** @scrutinizer ignore-type */ $queryExec) == 0) {
175
            throw new Exception("Attempt to construct an unknown DeploymentManaged!");
176
        }
177
        $this->identifier = $deploymentIdRaw;
178
        while ($iterator = mysqli_fetch_object(/** @scrutinizer ignore-type */ $queryExec)) {
179
            if ($iterator->secret == NULL && $iterator->radius_instance_1 == NULL) {
180
                // we are instantiated for the first time, initialise us
181
                $details = $this->initialise();
182
                $this->port1 = $details["port_instance_1"];
183
                $this->port2 = $details["port_instance_2"];
184
                $this->secret = $details["secret"];
185
                $this->radius_instance_1 = $details["radius_instance_1"];
186
                $this->radius_instance_2 = $details["radius_instance_2"];
187
                $this->radius_status_1 = 1;
188
                $this->radius_status_2 = 1;
189
                $this->status = AbstractDeployment::INACTIVE;
190
            } else {
191
                $this->port1 = $iterator->port_instance_1;
192
                $this->port2 = $iterator->port_instance_2;
193
                $this->secret = $iterator->secret;
194
                $this->radius_instance_1 = $iterator->radius_instance_1;
195
                $this->radius_instance_2 = $iterator->radius_instance_2;
196
                $this->radius_status_1 = $iterator->radius_status_1;
197
                $this->radius_status_2 = $iterator->radius_status_2;
198
                $this->status = $iterator->status;
199
            }
200
        }
201
        $server1details = $this->databaseHandle->exec("SELECT mgmt_hostname, radius_ip4, radius_ip6 FROM managed_sp_servers WHERE server_id = '$this->radius_instance_1'");
202
        while ($iterator2 = mysqli_fetch_object(/** @scrutinizer ignore-type */ $server1details)) {
203
            $this->host1_v4 = $iterator2->radius_ip4;
204
            $this->host1_v6 = $iterator2->radius_ip6;
205
            $this->radius_hostname_1 = $iterator2->mgmt_hostname;
206
        }
207
        $server2details = $this->databaseHandle->exec("SELECT mgmt_hostname, radius_ip4, radius_ip6 FROM managed_sp_servers WHERE server_id = '$this->radius_instance_2'");
208
        while ($iterator3 = mysqli_fetch_object(/** @scrutinizer ignore-type */ $server2details)) {
209
            $this->host2_v4 = $iterator3->radius_ip4;
210
            $this->host2_v6 = $iterator3->radius_ip6;
211
            $this->radius_hostname_2 = $iterator3->mgmt_hostname;
212
        }
213
        $thisLevelAttributes = $this->retrieveOptionsFromDatabase("SELECT DISTINCT option_name, option_lang, option_value, row 
214
                                            FROM $this->entityOptionTable
215
                                            WHERE $this->entityIdColumn = ?  
216
                                            ORDER BY option_name", "Profile");
217
        $tempAttribMergedIdP = $this->levelPrecedenceAttributeJoin($thisLevelAttributes, $this->idpAttributes, "IdP");
218
        $this->attributes = $this->levelPrecedenceAttributeJoin($tempAttribMergedIdP, $this->fedAttributes, "FED");
219
    }
220
221
    /**
222
     * finds a suitable server which is geographically close to the admin
223
     * 
224
     * @param array  $adminLocation      the current geographic position of the admin
225
     * @param string $federation         the federation this deployment belongs to
226
     * @param array  $blacklistedServers list of server to IGNORE
227
     * @return string the server ID
228
     * @throws Exception
229
     */
230
    private function findGoodServerLocation($adminLocation, $federation, $blacklistedServers) {
231
        // find a server near him (list of all servers with capacity, ordered by distance)
232
        // first, if there is a pool of servers specifically for this federation, prefer it
233
        $servers = $this->databaseHandle->exec("SELECT server_id, radius_ip4, radius_ip6, location_lon, location_lat FROM managed_sp_servers WHERE pool = '$federation'");
234
        
235
        $serverCandidates = [];
236
        while ($iterator = mysqli_fetch_object(/** @scrutinizer ignore-type */ $servers)) {
237
            $maxSupportedClients = DeploymentManaged::MAX_CLIENTS_PER_SERVER;
238
            if ($iterator->radius_ip4 == NULL || $iterator->radius_ip6 == NULL) {
239
                // half the amount of IP stacks means half the amount of FDs in use, so we can take twice as many
240
                $maxSupportedClients = $maxSupportedClients * 2;
241
            }
242
            $clientCount1 = $this->databaseHandle->exec("SELECT port_instance_1 AS tenants1 FROM deployment WHERE radius_instance_1 = '$iterator->server_id'");
243
            $clientCount2 = $this->databaseHandle->exec("SELECT port_instance_2 AS tenants2 FROM deployment WHERE radius_instance_2 = '$iterator->server_id'");
244
245
            $clients = $clientCount1->num_rows + $clientCount2->num_rows;
246
            if (in_array($iterator->server_id, $blacklistedServers)) {
247
                continue;
248
            }
249
            if ($clients < $maxSupportedClients) {
250
                $serverCandidates[IdPlist::geoDistance($adminLocation, ['lat' => $iterator->location_lat, 'lon' => $iterator->location_lon])] = $iterator->server_id;
251
            }
252
            if ($clients > $maxSupportedClients * 0.9) {
253
                $this->loggerInstance->debug(1, "A RADIUS server for Managed SP (" . $iterator->server_id . ") is serving at more than 90% capacity!");
254
            }
255
        }
256
        if (count($serverCandidates) == 0 && $federation != "DEFAULT") {
257
            // we look in the default pool instead
258
            // recursivity! Isn't that cool!
259
            return $this->findGoodServerLocation($adminLocation, "DEFAULT", $blacklistedServers);
260
        }
261
        if (count($serverCandidates) == 0) {
262
            throw new Exception("No available server found for new SP! $federation ".print_r($serverCandidates, true));
263
        }
264
        // put the nearest server on top of the list
265
        ksort($serverCandidates);
266
        $this->loggerInstance->debug(1, $serverCandidates);
267
        return array_shift($serverCandidates);
268
    }
269
270
    /**
271
     * initialises a new SP
272
     * 
273
     * @return array details of the SP as generated during initialisation
274
     * @throws Exception
275
     */
276
    private function initialise() {
277
        // find out where the admin is located approximately
278
        $ourLocation = ['lon' => 0, 'lat' => 0];
279
        $geoip = DeviceLocation::locateDevice();
280
        if ($geoip['status'] == 'ok') {
281
            $ourLocation = ['lon' => $geoip['geo']['lon'], 'lat' => $geoip['geo']['lat']];
282
        }
283
        $inst = new IdP($this->institution);
284
        $ourserver = $this->findGoodServerLocation($ourLocation, $inst->federation , []);
285
        // now, find an unused port in the preferred server
286
        $foundFreePort1 = 0;
287
        while ($foundFreePort1 == 0) {
288
            $portCandidate = random_int(1200, 65535);
289
            $check = $this->databaseHandle->exec("SELECT port_instance_1 FROM deployment WHERE radius_instance_1 = '" . $ourserver . "' AND port_instance_1 = $portCandidate");
290
            if (mysqli_num_rows(/** @scrutinizer ignore-type */ $check) == 0) {
291
                $foundFreePort1 = $portCandidate;
292
            }
293
        }
294
        $ourSecondServer = $this->findGoodServerLocation($ourLocation, $inst->federation , [$ourserver]);
295
        $foundFreePort2 = 0;
296
        while ($foundFreePort2 == 0) {
297
            $portCandidate = random_int(1200, 65535);
298
            $check = $this->databaseHandle->exec("SELECT port_instance_2 FROM deployment WHERE radius_instance_2 = '" . $ourSecondServer . "' AND port_instance_2 = $portCandidate");
299
            if (mysqli_num_rows(/** @scrutinizer ignore-type */ $check) == 0) {
300
                $foundFreePort2 = $portCandidate;
301
            }
302
        }
303
        // and make up a shared secret that is halfways readable
304
        $futureSecret = $this->randomString(16, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
305
        $this->databaseHandle->exec("UPDATE deployment SET radius_instance_1 = '" . $ourserver . "', radius_instance_2 = '" . $ourSecondServer . "', port_instance_1 = $foundFreePort1, port_instance_2 = $foundFreePort2, secret = '$futureSecret' WHERE deployment_id = $this->identifier");
306
        return ["port_instance_1" => $foundFreePort1, "port_instance_2" => $foundFreePort2, "secret" => $futureSecret, "radius_instance_1" => $ourserver, "radius_instance_2" => $ourserver];
307
    }
308
309
    /**
310
     * update the last_changed timestamp for this deployment
311
     * 
312
     * @return void
313
     */
314
    public function updateFreshness() {
315
        $this->databaseHandle->exec("UPDATE deployment SET last_change = CURRENT_TIMESTAMP WHERE deployment_id = $this->identifier");
316
    }
317
318
    /**
319
     * gets the last-modified timestamp (useful for caching "dirty" check)
320
     * 
321
     * @return string the date in string form, as returned by SQL
322
     */
323
    public function getFreshness() {
324
        $execLastChange = $this->databaseHandle->exec("SELECT last_change FROM deployment WHERE deployment_id = $this->identifier");
325
        // SELECT always returns a resource, never a boolean
326
        if ($freshnessQuery = mysqli_fetch_object(/** @scrutinizer ignore-type */ $execLastChange)) {
327
            return $freshnessQuery->last_change;
328
        }
329
    }
330
331
    /**
332
     * Deletes the deployment from database
333
     * 
334
     * @return void
335
     */
336
    public function destroy() {
337
        $this->databaseHandle->exec("DELETE FROM deployment_option WHERE deployment_id = $this->identifier");
338
        $this->databaseHandle->exec("DELETE FROM deployment WHERE deployment_id = $this->identifier");
339
    }
340
341
    /**
342
     * deactivates the deployment.
343
     * TODO: needs to call the RADIUS server reconfiguration routines...
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
344
     * 
345
     * @return void
346
     */
347
    public function deactivate() {
348
        $this->databaseHandle->exec("UPDATE deployment SET status = " . DeploymentManaged::INACTIVE . " WHERE deployment_id = $this->identifier");
349
    }
350
351
    /**
352
     * activates the deployment.
353
     * TODO: needs to call the RADIUS server reconfiguration routines...
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
354
     * 
355
     * @return void
356
     */
357
    public function activate() {
358
        $this->databaseHandle->exec("UPDATE deployment SET status = " . DeploymentManaged::ACTIVE . " WHERE deployment_id = $this->identifier");
359
    }
360
361
    /**
362
     * determines the Operator-Name attribute content to use in the RADIUS config
363
     * 
364
     * @return string
365
     */
366
    public function getOperatorName() {
367
        $customAttrib = $this->getAttributes("managedsp:operatorname");
368
        if (count($customAttrib) == 0) {
369
            return "1sp.".$this->identifier."-".$this->institution.\config\ConfAssistant::SILVERBULLET['realm_suffix'];
370
        }
371
        return $customAttrib[0]["value"];
372
    }
373
    /**
374
     * check whether the configured RADIUS hosts actually exist
375
     * 
376
     * @param  integer $idx  server index 1 (primary) or 2 (backup)
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
377
     * @return boolean or NULL
378
     */
379
    public function checkRADIUSHost($idx) {
380
        if ($idx == 1) {
381
            $host = $this->radius_hostname_1;
382
        } elseif ($idx == 2) {
383
            $host = $this->radius_hostname_2;
384
        } else {
385
            return NULL;
386
        }
387
        $statusServer = new diag\RFC5997Tests($host, 1999, $this->secret);
388
        $this->loggerInstance->debug(1, $statusServer);
389
        if ($statusServer->statusServerCheck() === diag\AbstractTest::RETVAL_OK) {
390
            $this->loggerInstance->debug(1, "YESSSSS");
391
            return TRUE;
392
        }
393
        $this->loggerInstance->debug(1, "NOOOOO");
394
        return FALSE;
395
    }
396
    
397
    /**
398
     * send request to RADIUS configuration daemom
399
     *
400
     * @param  integer $idx  server index 1 (primary) or 2 (backup)
401
     * @param  string  $post string to POST 
402
     * @return string  OK or FAILURE
403
     */
404
    public function sendToRADIUS($idx, $post) {
405
            
406
        $hostname = "radius_hostname_$idx";
407
        $ch = curl_init( "http://" . $this->$hostname );
408
        if ($ch === FALSE) {
409
            $res = 'FAILURE';
410
        } else {
411
            curl_setopt( $ch, CURLOPT_POST, 1);
412
            curl_setopt( $ch, CURLOPT_POSTFIELDS, $post);
413
            $this->loggerInstance->debug(1, "Posting to http://" . $this->$hostname . ": $post\n");
414
            curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1);
415
            curl_setopt( $ch, CURLOPT_HEADER, 0);
416
            curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1);
417
            $exec = curl_exec( $ch );
418
            if ($exec === FALSE) {
419
                $res = 'FAILURE';
420
            } else {
421
                $res = $exec;
422
            }
423
            $this->loggerInstance->debug(1, "Response from FR configurator: $res\n");
424
            $this->loggerInstance->debug(1, $this);           
425
        }
426
        $this->databaseHandle->exec("UPDATE deployment SET radius_status_$idx = " . ($res == 'OK'? \core\AbstractDeployment::RADIUS_OK : \core\AbstractDeployment::RADIUS_FAILURE) . " WHERE deployment_id = $this->identifier");
427
        return $res;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $res also could return the type true which is incompatible with the documented return type string.
Loading history...
428
    }
429
    /**
430
     * prepare sent email message to support mail
431
     *
432
     * @param  int   $remove   the flag indicating remove request
433
     * @param  array $response setRADIUSconfig result
434
     * @return void
435
     * 
436
     */
437
    private function sendMailtoAdmin($remove, $response) {
438
        $txt = '';
439
        if ($remove) {
440
            $txt = _('Profile dectivation failed' . ' ');
441
        } else {
442
            $txt = _('Profile activation/modification failed' . ' ');
443
        }
444
        if (array_count_values($response)['FAILURE'] == 2) {
445
            $txt = $txt . _('on both RADIUS servers: primary and backup') . '.';
446
        } else {
447
            if ($response['res[1]'] == 'FAILURE') {
448
                $txt = $txt . _('on primary RADIUS server') . '.';
449
            } else {
450
                $txt = $txt . _('on backup RADIUS server') . '.';
451
            }
452
        }
453
        $mail = \core\common\OutsideComm::mailHandle();
454
        $email = $this->getAttributes("support:email")[0]['value'];
455
        $mail->FromName = \config\Master::APPEARANCE['productname'] . " Notification System";
456
        $mail->addAddress($email);
457
        $mail->Subject = _('RADIUS profile update problem');
458
        $mail->Body = $txt;
459
460
        $sent = $mail->send();
461
        if ( $sent === FALSE)
462
        $this->loggerInstance->debug(1, 'Mailing on RADIUS problem failed');
463
    }
464
    /**
465
     * check if URL responds with 200
466
     *
467
     * @param integer $idx server index 1 (primary) or 2 (backup)
468
     * @return integer or NULL
469
     */
470
    private function checkURL ($idx) {
471
        $ch = curl_init();
472
        if ($ch === FALSE) {
473
            return NULL;
474
        }
475
        if ($idx == 1) {
476
            $host = $this->radius_hostname_1;
477
        } elseif ($idx == 2) {
478
            $host = $this->radius_hostname_2;
479
        } else {
480
            return NULL;
481
        }
482
        $timeout = 10;
483
        curl_setopt ( $ch, CURLOPT_URL, 'http://'.$host );
484
        curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, 1 );
485
        curl_setopt ( $ch, CURLOPT_TIMEOUT, $timeout );
486
        curl_exec($ch);
487
        $http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
488
        if ($http_code == 200) {
489
            return 1;
490
        }
491
        return 0;
492
    }
493
    /**
494
     * check if RADIUS configuration deamon is listening for requests
495
     *
496
     * @return array index res[1] indicate primary RADIUS status, index res[2] backup RADIUS status
497
     */
498
    public function checkRADIUSconfigDaemon() {
499
        $timeout = 10;
0 ignored issues
show
Unused Code introduced by
The assignment to $timeout is dead and can be removed.
Loading history...
500
        $res = array();
501
        if ($this->radius_status_1 == \core\AbstractDeployment::RADIUS_FAILURE) {
502
            $res[1] = $this->checkURL(1);
503
        }
504
        if ($this->radius_status_2 == \core\AbstractDeployment::RADIUS_FAILURE) {
505
            $res[2] = $this->checkURL(2);
506
        }
507
        return $res;
508
    }
509
    /**
510
     * prepare request to add/modify RADIUS settings for given deployment
511
     *
512
     * @param int $remove  the flag indicating that it is remove request
513
     * @param int $onlyone the flag indicating on which server to conduct modifications
514
     * @return array index res[1] indicate primary RADIUS status, index res[2] backup RADIUS status
515
     */
516
    public function setRADIUSconfig($remove = 0, $onlyone = 0) {
517
        $toPost = array();
518
        if ($onlyone) {
519
            $toPost[$onlyone]  = '';
520
        } else {
521
            // run on both servers
522
            $toPost[1] = '';
523
            $toPost[2] = '';
524
        }
525
        $toPostTemplate = 'instid=' . $this->institution . '&deploymentid=' . $this->identifier . '&secret=' . $this->secret . '&country=' . $this->getAttributes("internal:country")[0]['value'] . '&';
526
        if ($remove) {
527
            $toPostTemplate = $toPostTemplate . 'remove=1&';
528
        } else {
529
            if ($this->getAttributes("managedsp:operatorname")[0]['value'] ?? NULL) {
530
                $toPostTemplate = $toPostTemplate . 'operatorname=' . $this->getAttributes("managedsp:operatorname")[0]['value'] . '&';
531
            }
532
            if ($this->getAttributes("managedsp:vlan")[0]['value'] ?? NULL) {
533
                $idp = new IdP($this->institution);
534
                $allProfiles = $idp->listProfiles(TRUE);
535
                $allRealms = [];
536
                if (($this->getAttributes("managedsp:realmforvlan") ?? NULL)) {
537
                    $allRealms = array_values(array_unique(array_column($this->getAttributes("managedsp:realmforvlan"), "value")));
538
                }
539
                foreach ($allProfiles as $profile) {
540
                    if ($realm = ($profile->getAttributes("internal:realm")[0]['value'] ?? NULL)) {
541
                        if (!in_array($realm, $allRealms)) {
542
                            $allRealms[] = $realm;
543
                        }
544
                    }
545
                }
546
                if (!empty($allRealms)) {
547
                    $toPostTemplate = $toPostTemplate . 'vlan=' . $this->getAttributes("managedsp:vlan")[0]['value'] . '&';
548
                    $toPostTemplate = $toPostTemplate . 'realmforvlan[]=' . implode('&realmforvlan[]=', $allRealms) . '&';
549
                }
550
            }
551
        }
552
        foreach (array_keys($toPost) as $key) {
553
            if ($key == 1) {
554
                $toPost[$key] = $toPostTemplate . 'port=' . $this->port1;
555
            } else {
556
                $toPost[$key] = $toPostTemplate . 'port=' . $this->port2;
557
            }      
558
        }
559
        $response = array();
560
        foreach ($toPost as $key => $value) {
561
            $this->loggerInstance->debug(1, 'toPost ' . $toPost[$key] ."\n");
562
            $response['res['.$key.']'] = $this->sendToRADIUS($key, $toPost[$key]);
563
        }
564
        if ($onlyone) {
565
            $response['res['.($onlyone==1)? 2 : 1 . ']'] = \core\AbstractDeployment::RADIUS_OK;
566
        }
567
        if (in_array('FAILURE', $response)) {
568
            $this->sendMailtoAdmin($remove, $response);
569
        }
570
        return $response;
571
    }
572
}
573