DeviceConfig   F
last analyzed

Complexity

Total Complexity 110

Size/Duplication

Total Lines 824
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 110
eloc 292
c 0
b 0
f 0
dl 0
loc 824
rs 2

23 Methods

Rating   Name   Duplication   Size   Complexity  
A getAttribute() 0 3 2
A copyFile() 0 16 4
A customTranslit() 0 7 1
A writeDeviceInfo() 0 6 1
B saveCertificateFiles() 0 34 7
A calculatePreferredEapType() 0 11 4
A getDeviceId() 0 10 3
B getInstallerBasename() 0 33 9
A findSourceFile() 0 9 3
A setSupportedEapMethods() 0 5 1
A getConsortia() 0 15 5
A saveLogoFile() 0 26 4
B longestNameSuffix() 0 28 7
A __construct() 0 3 1
A getProfileAttributes() 0 11 2
A getConfigSSIDs() 0 12 4
A getConfigOIs() 0 12 4
A dumpAttributes() 0 6 1
A determineOuterIdString() 0 10 5
A saveInfoFile() 0 13 3
C getNetworks() 0 36 14
F setup() 0 84 18
B getSSIDs() 0 26 7

How to fix   Complexity   

Complex Class

Complex classes like DeviceConfig often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DeviceConfig, and based on these observations, apply Extract Interface, too.

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 defines the abstract Device class
25
 *
26
 * @package ModuleWriting
27
 */
28
/**
29
 * 
30
 */
31
32
namespace core;
33
34
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...
35
36
/**
37
 * This class defines the API for CAT module writers.
38
 *
39
 * A device is a fairly abstract notion. In most cases it represents
40
 * a particular operating system or a set of operating systems
41
 * like MS Windows Vista and newer.
42
 *
43
 * The purpose of this class is to prepare a setup for the device configurator,
44
 * collect all necessary information from the database, taking into account
45
 * limitations, that a given device may present (like a set of supported EAP methods).
46
 *
47
 * All that is required from the device module is to produce a configurator
48
 * file and pass its name back to the API.
49
 *
50
 * 
51
 * @author Tomasz Wolniewicz <[email protected]>
52
 *
53
 * @license see LICENSE file in root directory
54
 * 
55
 * @package ModuleWriting
56
 * @abstract
57
 */
58
abstract class DeviceConfig extends \core\common\Entity
59
{
60
61
    /**
62
     * stores the path to the temporary working directory for a module instance
63
     * @var string $FPATH
64
     */
65
    public $FPATH;
66
67
    /**
68
     * array of specialities - will be displayed on the admin download as "footnote"
69
     * @var array specialities
70
     */
71
    public $specialities;
72
73
    /**
74
     * list of supported EAP methods
75
     * @var array EAP methods
76
     */
77
    public $supportedEapMethods;
78
 
79
    /**
80
     * 
81
     * @var string the realm attached to the profile (possibly substituted with fallback value
82
     */
83
    public $realm = NULL;
84
    
85
    /**
86
     * sets the supported EAP methods for a device
87
     * 
88
     * @param array $eapArray the list of EAP methods the device supports
89
     * @return void
90
     */
91
    protected function setSupportedEapMethods($eapArray)
92
    {
93
        $this->supportedEapMethods = $eapArray;
94
        $this->loggerInstance->debug(4, "This device (" . __CLASS__ . ") supports the following EAP methods: ");
95
        $this->loggerInstance->debug(4, $this->supportedEapMethods);
96
    }
97
98
    /**
99
     * device module constructor should be defined by each module. 
100
     * The one important thing to do is to call setSupportedEapMethods with an 
101
     * array of EAP methods the device supports
102
     */
103
    public function __construct()
104
    {
105
        parent::__construct();
106
    }
107
108
    /**
109
     * given one or more server name strings, calculate the suffix that is common
110
     * to all of them
111
     * 
112
     * Examples:
113
     * 
114
     * ["host.somewhere.com", "gost.somewhere.com"] => "ost.somewhere.com"
115
     * ["my.server.name"] => "my.server.name"
116
     * ["foo.bar.de", "baz.bar.ge"] => "e"
117
     * ["server1.example.com", "server2.example.com", "serverN.example.com"] => ".example.com"
118
119
     * @return string
120
     */
121
    public function longestNameSuffix()
122
    {
123
        // for all configured server names, find the string that is the longest
124
        // suffix to all of them
125
        $longestSuffix = "";
126
        if (!isset($this->attributes["eap:server_name"])) {
127
            return "";
128
        }
129
        $numStrings = count($this->attributes["eap:server_name"]);
130
        if ($numStrings == 0) {
131
            return "";
132
        }
133
        // always take the candidate character from the first array element, and
134
        // verify whether the other elements have that character in the same 
135
        // position, too
136
        while (TRUE) {
137
            if ($longestSuffix == $this->attributes["eap:server_name"][0]) {
138
                break;
139
            }
140
            $candidate = substr($this->attributes["eap:server_name"][0], -(strlen($longestSuffix) + 1), 1);
141
            for ($iterator = 1; $iterator < $numStrings; $iterator++) {
142
                if (substr($this->attributes["eap:server_name"][$iterator], -(strlen($longestSuffix) + 1), 1) != $candidate) {
143
                    break 2;
144
                }
145
            }
146
            $longestSuffix = $candidate.$longestSuffix;
147
        }
148
        return $longestSuffix;
149
    }
150
151
    /**
152
     * Set up working environment for a device module
153
     *
154
     * Sets up the device module environment taking into account the actual profile
155
     * selected by the user in the GUI. The selected profile is passed as the
156
     * Profile $profile argument.
157
     *
158
     * This method needs to be called after the device instance has been created (the GUI class does that)
159
     *
160
     * setup performs the following tasks:
161
     * - collect profile attributes and pass them as the attributes property;
162
     * - create the temporary working directory
163
     * - process CA certificates and store them as 'internal:CAs' attribute
164
     * - process and save optional info files and store references to them in
165
     *   'internal:info_file' attribute
166
     * @param AbstractProfile $profile        the profile object which will be passed by the caller
167
     * @param string          $token          the invitation token for silverbullet requests
168
     * @param string          $importPassword the PIN for the installer for silverbullet requests
169
     * @return void
170
     * @throws Exception
171
     * @final not to be redefined
172
     */
173
    final public function setup(AbstractProfile $profile, $token = NULL, $importPassword = NULL, $openRoaming = 0)
174
    {
175
        $this->loggerInstance->debug(4, "module setup start\n");
176
        common\Entity::intoThePotatoes();
177
        $purpose = 'installer';
178
        $eaps = $profile->getEapMethodsinOrderOfPreference(1);
179
        $this->calculatePreferredEapType($eaps);
180
        if (count($this->selectedEap) == 0) {
181
            throw new Exception("No EAP type available.");
182
        }
183
        $this->attributes = $this->getProfileAttributes($profile);
184
        $this->deviceUUID = common\Entity::uuid('', 'CAT'.$profile->institution."-".$profile->identifier."-".$this->device_id);
185
186
        if (isset($this->attributes['internal:use_anon_outer']) && $this->attributes['internal:use_anon_outer'][0] == "1" && isset($this->attributes['internal:realm'])) {
187
            $this->realm = $this->attributes['internal:realm'][0];
188
        }
189
        // if we are instantiating a Silverbullet profile AND have been given
190
        // a token, attempt to create the client certificate NOW
191
        // then, this is the only instance of the device ever which knows the
192
        // cert and private key. It's not saved anywhere, so it's gone forever
193
        // after code execution!
194
195
        $this->loggerInstance->debug(5, "DeviceConfig->setup() - preliminaries done.\n");
196
        if ($profile instanceof ProfileSilverbullet && $token !== NULL && $importPassword !== NULL) {
197
            $this->clientCert = SilverbulletCertificate::issueCertificate($token, $importPassword, $this->options['clientcert']);
198
            // we need to drag this along; ChromeOS needs it outside the P12 container to encrypt the entire *config* with it.
199
            // Because encrypted private keys are not supported as per spec!
200
            $purpose = 'silverbullet';
201
            // let's keep a record for which device type this token was consumed
202
            $dbInstance = DBConnection::handle("INST");
203
            $certId = $this->clientCert['certObject']->dbId;
204
            $this->attributes['internal:username'] = [$this->clientCert['CN']];
205
            $dbInstance->exec("UPDATE `silverbullet_certificate` SET `device` = ? WHERE `id` = ?", "si", $this->device_id, $certId);
206
        }
207
        $this->loggerInstance->debug(5, "DeviceConfig->setup() - silverbullet checks done.\n");
208
        // create temporary directory, its full path will be saved in $this->FPATH;
209
        $tempDir = \core\common\Entity::createTemporaryDirectory($purpose);
210
        $this->FPATH = $tempDir['dir'];
211
        mkdir($tempDir['dir'].'/tmp');
212
        chdir($tempDir['dir'].'/tmp');
213
        $caList = [];
214
        $x509 = new \core\common\X509();
215
        if (isset($this->attributes['eap:ca_file'])) {
216
            foreach ($this->attributes['eap:ca_file'] as $ca) {
217
                $processedCert = $x509->processCertificate($ca);
218
                if (is_array($processedCert)) {
219
                    // add a UUID for convenience (some devices refer to their CAs by a UUID value)
220
                    $processedCert['uuid'] = common\Entity::uuid("", $processedCert['pem']);
221
                    $caList[] = $processedCert;
222
                }
223
            }
224
            $this->attributes['internal:CAs'][0] = $caList;
225
        }
226
227
        if (isset($this->attributes['support:info_file'])) {
228
            $this->attributes['internal:info_file'][0] = $this->saveInfoFile($this->attributes['support:info_file'][0]);
229
        }
230
        if (isset($this->attributes['general:logo_file'])) {
231
            $this->loggerInstance->debug(5, "saving IDP logo\n");
232
            $this->attributes['internal:logo_file'] = $this->saveLogoFile($this->attributes['general:logo_file'], 'idp');
233
        }
234
        if (isset($this->attributes['fed:logo_file'])) {
235
            $this->loggerInstance->debug(5, "saving FED logo\n");
236
            $this->attributes['fed:logo_file'] = $this->saveLogoFile($this->attributes['fed:logo_file'], 'fed');
237
        }
238
        $this->attributes['internal:SSID'] = $this->getSSIDs()['add'];
239
240
        $this->attributes['internal:remove_SSID'] = $this->getSSIDs()['del'];
241
242
        $this->attributes['internal:consortia'] = $this->getConsortia();
243
        if ($openRoaming == 1 && isset($this->attributes['media:openroaming'])) {
244
            $this->attributes['internal:openroaming'] = TRUE;
245
        }
246
        
247
        $this->attributes['internal:networks'] = $this->getNetworks();
248
249
        $this->support_email_substitute = sprintf(_("your local %s support"), \config\ConfAssistant::CONSORTIUM['display_name']);
250
        $this->support_url_substitute = sprintf(_("your local %s support page"), \config\ConfAssistant::CONSORTIUM['display_name']);
251
252
        if ($this->signer && $this->options['sign']) {
253
            $this->sign = ROOT.'/signer/'.$this->signer;
254
        }
255
        $this->installerBasename = $this->getInstallerBasename();
256
        common\Entity::outOfThePotatoes();
257
    }
258
259
    /**
260
     * Selects the preferred eap method based on profile EAP configuration and device EAP capabilities
261
     *
262
     * @param array $eapArrayofObjects an array of eap methods supported by a given device
263
     * @return void
264
     */
265
    public function calculatePreferredEapType($eapArrayofObjects)
266
    {
267
        $this->selectedEap = [];
268
        foreach ($eapArrayofObjects as $eap) {
269
            if (in_array($eap->getArrayRep(), $this->supportedEapMethods)) {
270
                $this->selectedEap = $eap->getArrayRep();
271
                break;
272
            }
273
        }
274
        if ($this->selectedEap != []) {
275
            $this->selectedEapObject = new common\EAP($this->selectedEap);
276
        }
277
    }
278
279
    /**
280
     * prepare usage information for the installer
281
     * every device module should override this method
282
     *
283
     * @return string HTML text to be displayed
284
     */
285
    public function writeDeviceInfo()
286
    {
287
        common\Entity::intoThePotatoes();
288
        $retval = _("Sorry, this should not happen - no additional information is available");
289
        common\Entity::outOfThePotatoes();
290
        return $retval;
291
    }
292
293
    /**
294
     * function to return exactly one attribute type
295
     * 
296
     * @param string $attrName the attribute to retrieve
297
     * @return array|NULL the attributes
298
     */
299
    public function getAttribute($attrName)
300
    {
301
        return empty($this->attributes[$attrName]) ? NULL : $this->attributes[$attrName];
302
    }
303
304
    /**
305
     * some modules have a complex directory structure. This helper finds resources
306
     * in that structure. Mostly used in the Windows modules.
307
     * 
308
     * @param  string $file the filename to search for (without path)
309
     * @return string|boolean the filename as found, with path, or FALSE if it does not exist
310
     */
311
    protected function findSourceFile($file)
312
    {
313
        if (is_file($this->module_path.'/Files/'.$this->device_id.'/'.$file)) {
314
            return $this->module_path.'/Files/'.$this->device_id.'/'.$file;
315
        } elseif (is_file($this->module_path.'/Files/'.$file)) {
316
            return $this->module_path.'/Files/'.$file;
317
        } else {
318
            $this->loggerInstance->debug(2, "requested file $file does not exist\n");
319
            return FALSE;
320
        }
321
    }
322
323
    /**
324
     *  Copy a file from the module location to the temporary directory.
325
     *
326
     * If the second argument is provided then the file will be saved under the name 
327
     * taken form this argument. If only one parameter is given, source and destination
328
     * filenames are the same
329
     * Source file can be located either in the Files subdirectory or in the sibdirectory of Files
330
     * named the same as device_id. The second option takes precedence.
331
     *
332
     * @param string $source_name The source file name
333
     * @param string $output_name The destination file name
334
     *
335
     * @return boolean result of the copy operation
336
     * @final not to be redefined
337
     */
338
    final protected function copyFile($source_name, $output_name = NULL)
339
    {
340
        if ($output_name === NULL) {
341
            $output_name = $source_name;
342
        }
343
        $this->loggerInstance->debug(5, "fileCopy($source_name, $output_name)\n");
344
        $source = $this->findSourceFile($source_name);
345
        if ($source === FALSE) {
346
            return FALSE;
347
        }
348
        $this->loggerInstance->debug(5, "Copying $source to $output_name\n");
349
        $result = copy($source, "$output_name");
350
        if (!$result) {
351
            $this->loggerInstance->debug(2, "fileCopy($source_name, $output_name) failed\n");
352
        }
353
        return($result);
354
    }
355
356
    /**
357
     * Save certificate files in either DER or PEM format
358
     *
359
     * Certificate files will be saved in the module working directory.
360
     * 
361
     * saved certificate file names are available under the 'file' index
362
     * additional array entries are indexed as 'sha1', 'md5', and 'root'.
363
     * sha1 and md5 are corresponding certificate hashes
364
     * root is set to 1 for the CA roor certificate and 0 otherwise
365
     * 
366
     * @param string $format only "der" and "pem" are currently allowed
367
     * @return array an array of arrays or empty array on error
368
     * @throws Exception
369
     */
370
    final protected function saveCertificateFiles($format)
371
    {
372
        switch ($format) {
373
            case "der": // fall-thorugh, same treatment
374
            case "pem":
375
                $iterator = 0;
376
                $caFiles = [];
377
                $caArray = $this->attributes['internal:CAs'][0];
378
                if (!$caArray) {
379
                    return([]);
380
                }
381
                foreach ($caArray as $certAuthority) {
382
                    $fileHandle = fopen("cert-$iterator.crt", "w");
383
                    if (!$fileHandle) {
384
                        throw new Exception("problem opening the file");
385
                    }
386
                    if ($format === "pem") {
387
                        fwrite($fileHandle, $certAuthority['pem']);
388
                    } else {
389
                        fwrite($fileHandle, $certAuthority['der']);
390
                    }
391
                    fclose($fileHandle);
392
                    $certAuthorityProps = [];
393
                    $certAuthorityProps['file'] = "cert-$iterator.crt";
394
                    $certAuthorityProps['sha1'] = $certAuthority['sha1'];
395
                    $certAuthorityProps['md5'] = $certAuthority['md5'];
396
                    $certAuthorityProps['root'] = $certAuthority['root'];
397
                    $caFiles[] = $certAuthorityProps;
398
                    $iterator++;
399
                }
400
                return($caFiles);
401
            default:
402
                $this->loggerInstance->debug(2, 'incorrect format value specified');
403
                return([]);
404
        }
405
    }
406
407
    /**
408
     * set of characters to remove from filename strings
409
     */
410
    private const TRANSLIT_SCRUB = '/[ ()\/\'"]+/';
411
412
    /**
413
     * Does a transliteration from UTF-8 to ASCII to get a sane filename
414
     * Takes special characters into account, and always uses English CTYPE
415
     * to avoid introduction of funny characters due to "flying accents"
416
     * 
417
     * @param string $input the input string that is to be transliterated
418
     * @return string the transliterated string
419
     */
420
    private function customTranslit($input)
421
    {
422
        $oldlocale = setlocale(LC_CTYPE, 0);
423
        setlocale(LC_CTYPE, "en_US.UTF-8");
424
        $retval = preg_replace(DeviceConfig::TRANSLIT_SCRUB, '_', iconv("UTF-8", "US-ASCII//TRANSLIT", $input));
425
        setlocale(LC_CTYPE, $oldlocale);
426
        return $retval;
427
    }
428
429
    /**
430
     * Generate installer filename base.
431
     * Device module should use this name adding an extension.
432
     * Normally the device identifier follows the Consortium name.
433
     * The string taken for the device identifier equals (by default) to the index in the listDevices array,
434
     * but can be overridden with the 'device_id' device option.
435
     * 
436
     * @return string
437
     */
438
    private function getInstallerBasename()
439
    {
440
        $baseName = $this->customTranslit(\config\ConfAssistant::CONSORTIUM['name'])."-".$this->getDeviceId();
441
        if (isset($this->attributes['profile:customsuffix'][1])) {
442
            // this string will end up as a filename on a filesystem, so always
443
            // take a latin-based language variant if available
444
            // and then scrub non-ASCII just in case
445
            return $baseName.$this->customTranslit($this->attributes['profile:customsuffix'][1]);
446
        }
447
        // Okay, no custom suffix. 
448
        // Use the configured inst name and apply shortening heuristics
449
        // if an instshortname is set, base on that, otherwise, the normal instname
450
        $attribToUse = (isset($this->attributes['general:instshortname']) ? 'general:instshortname' : 'general:instname');
451
        $lang_pointer = \config\Master::LANGUAGES[$this->languageInstance->getLang()]['latin_based'] == TRUE ? 0 : 1;
452
        $this->loggerInstance->debug(5, "getInstallerBasename1:".$this->attributes[$attribToUse][$lang_pointer]."\n");
453
        $inst = $this->customTranslit($this->attributes[$attribToUse][$lang_pointer]);
454
        $this->loggerInstance->debug(4, "getInstallerBasename2:$inst\n");
455
        $Inst_a = explode('_', $inst);
456
        if (count($Inst_a) > 2) {
457
            $inst = '';
458
            foreach ($Inst_a as $i) {
459
                $inst .= $i[0];
460
            }
461
        }
462
        // and if the inst has multiple profiles, add the profile name behin
463
        if ($this->attributes['internal:profile_count'][0] > 1) {
464
            if (!empty($this->attributes['profile:name']) && !empty($this->attributes['profile:name'][$lang_pointer])) {
465
                $profTemp = $this->customTranslit($this->attributes['profile:name'][$lang_pointer]);
466
                $prof = preg_replace('/_+$/', '', $profTemp);
467
                return $baseName.$inst.'-'.$prof;
468
            }
469
        }
470
        return $baseName . $inst;
471
    }
472
473
    /**
474
     * returns the device_id of the current device
475
     * 
476
     * @return string
477
     */
478
    private function getDeviceId()
479
    {
480
        $deviceId = $this->device_id;
481
        if (isset($this->options['device_id'])) {
482
            $deviceId = $this->options['device_id'];
483
        }
484
        if ($deviceId !== '') {
485
            $deviceId .= '-';
486
        }
487
        return $deviceId;
488
    }
489
490
    /**
491
     * returns the list of SSIDs that installers should treat. 
492
     * 
493
     * Includes both SSIDs to be set up (and whether it's a TKIP-mixed or AES-only SSID) and SSIDs to be deleted
494
     * 
495
     * @return array
496
     */
497
    private function getSSIDs()
498
    {
499
        $ssidList = [];
500
        $ssidList['add'] = [];
501
        $ssidList['del'] = [];
502
        
503
        if (isset(\config\ConfAssistant::CONSORTIUM['ssid'])) {
504
            foreach (\config\ConfAssistant::CONSORTIUM['ssid'] as $ssid) {
505
                $ssidList['add'][$ssid] = 'AES';
506
                $ssidList['del'][$ssid] = 'TKIP';
507
            }
508
        }
509
        if (isset($this->attributes['media:SSID'])) {
510
            $ssidWpa2 = $this->attributes['media:SSID'];
511
512
            foreach ($ssidWpa2 as $ssid) {
513
                $ssidList['add'][$ssid] = 'AES';
514
            }
515
        }
516
        if (isset($this->attributes['media:remove_SSID'])) {
517
            $ssidRemove = $this->attributes['media:remove_SSID'];
518
            foreach ($ssidRemove as $ssid) {
519
                $ssidList['del'][$ssid] = 'DEL';
520
            }
521
        }
522
        return $ssidList;
523
    }
524
525
    /**
526
     * returns the list of Hotspot 2.0 / Passpoint roaming consortia to set up
527
     * 
528
     * @return array
529
     */
530
    private function getConsortia()
531
    {
532
533
        if (!isset(\config\ConfAssistant::CONSORTIUM['interworking-consortium-oi'])) {
534
            return ([]);
535
        }
536
        $consortia = \config\ConfAssistant::CONSORTIUM['interworking-consortium-oi'];
537
        if (isset($this->attributes['media:consortium_OI'])) {
538
            foreach ($this->attributes['media:consortium_OI'] as $new_oi) {
539
                if (!in_array($new_oi, $consortia)) {
540
                    $consortia[] = $new_oi;
541
                }
542
            }
543
        }
544
        return $consortia;
545
    }
546
    
547
    /**
548
     * return a list of SSIDs defined in the Config networks block
549
     * 
550
     * @return array $ssids
551
     */
552
    private function getConfigSSIDs()
553
    {
554
        $ssids = [];
555
        if (!isset(\config\ConfAssistant::CONSORTIUM['networks'])) {
556
            return [];
557
        }
558
        foreach (\config\ConfAssistant::CONSORTIUM['networks'] as $oneNetwork) {
559
            if (!empty($oneNetwork['ssid'])) {
560
                $ssids = array_merge($ssids, $oneNetwork['ssid']);
561
            }
562
        }
563
        return $ssids;
564
    }
565
    
566
    /**
567
     * return a list of OIs defined in the Config networks block
568
     * 
569
     * @return array $ois
570
     */
571
    private function getConfigOIs()
572
    {
573
        $ois = [];
574
        if (!isset(\config\ConfAssistant::CONSORTIUM['networks'])) {
575
            return [];
576
        }
577
        foreach (\config\ConfAssistant::CONSORTIUM['networks'] as $oneNetwork) {
578
            if (!empty($oneNetwork['oi'])) {
579
                $ois = array_merge($ois, $oneNetwork['oi']);
580
            }
581
        }
582
        return $ois;
583
    }
584
585
    /**
586
     * returns the list of parameters for predefined networks to be configured
587
     * 
588
     * @return array
589
     */
590
    private function getNetworks()
591
    {
592
        $additionalConsortia = [];
593
        $additionalSSIDs = [];
594
        $ssids = $this->getConfigSSIDs();
595
        $ois = $this->getConfigOIs();
596
        $networks = [];
597
        $realm = $this->realm === NULL ? \config\ConfAssistant::CONSORTIUM['interworking-domainname-fallback'] : $this->realm;
0 ignored issues
show
Unused Code introduced by
The assignment to $realm is dead and can be removed.
Loading history...
598
        foreach (\config\ConfAssistant::CONSORTIUM['networks'] ?? [] as $netName => $netDetails) {
599
            $netName = preg_replace('/%REALM%/', $this->realm, $netName);
600
            // only add network blocks if their respective condition is met in this profile
601
            if ($netDetails['condition'] === TRUE || (isset($this->attributes[$netDetails['condition']]) && $this->attributes[$netDetails['condition']] === TRUE)) { 
602
                $networks[$netName] = $netDetails;
603
                $this->loggerInstance->debug(5,$netName, "\nAdding network: ");
604
            }
605
        }
606
        // add locally defined SSIDs
607
        if (isset($this->attributes['media:SSID'])) {
608
            foreach ($this->attributes['media:SSID'] as $ssid) {
609
                if (!in_array($ssid, $ssids)) {
610
                    $additionalSSIDs[] = $ssid;
611
                }
612
            }
613
        }
614
        // add locally defined OIs
615
        if (isset($this->attributes['media:consortium_OI'])) {
616
            foreach ($this->attributes['media:consortium_OI'] as $new_oi) {
617
                if (!in_array($new_oi, $ois)) {
618
                    $additionalConsortia[] = $new_oi;
619
                }
620
            }
621
        }
622
        if (!empty($additionalConsortia) || !empty($additionalSSIDs)) {
623
            $networks[sprintf('%s Custom Network', CAT::$nomenclature_participant)] = ['ssid' => $additionalSSIDs, 'oi' => $additionalConsortia, 'condition' => 'locally_defined'];
624
        }
625
        return $networks;
626
    }
627
628
    /**
629
     * An array with shorthand definitions for MIME types
630
     * @var array
631
     */
632
    private $mime_extensions = [
633
        'text/plain' => 'txt',
634
        'text/rtf' => 'rtf',
635
        'application/pdf' => 'pdf',
636
    ];
637
638
    /**
639
     * saves a number of logos to a cache directory on disk.
640
     * 
641
     * @param array  $logos list of logos (binary strings each)
642
     * @param string $type  a qualifier what type of logo this is
643
     * @return array list of filenames and the mime types
644
     * @throws Exception
645
     */
646
    private function saveLogoFile($logos, $type)
647
    {
648
        $iterator = 0;
649
        $returnarray = [];
650
        foreach ($logos as $blob) {
651
            $finfo = new \finfo(FILEINFO_MIME_TYPE);
652
            $mime = $finfo->buffer($blob);
653
            $matches = [];
654
            if (preg_match('/^image\/(.*)/', $mime, $matches)) {
655
                $ext = $matches[1];
656
            } else {
657
                $ext = 'unsupported';
658
            }
659
            $this->loggerInstance->debug(5, "saveLogoFile: $mime : $ext\n");
660
            $fileName = 'logo-'.$type.$iterator.'.'.$ext;
661
            $fileHandle = fopen($fileName, "w");
662
            if (!$fileHandle) {
663
                $this->loggerInstance->debug(2, "saveLogoFile failed for: $fileName\n");
664
                throw new Exception("problem opening the file");
665
            }
666
            fwrite($fileHandle, $blob);
667
            fclose($fileHandle);
668
            $returnarray[] = ['name' => $fileName, 'mime' => $ext];
669
            $iterator++;
670
        }
671
        return($returnarray);
672
    }
673
674
    /**
675
     * saves the Terms of Use file onto disk
676
     * 
677
     * @param string $blob the Terms of Use
678
     * @return array with one entry, containing the filename and mime type
679
     * @throws Exception
680
     */
681
    private function saveInfoFile($blob)
682
    {
683
        $finfo = new \finfo(FILEINFO_MIME_TYPE);
684
        $mime = $finfo->buffer($blob);
685
        $ext = isset($this->mime_extensions[$mime]) ? $this->mime_extensions[$mime] : 'usupported';
686
        $this->loggerInstance->debug(5, "saveInfoFile: $mime : $ext\n");
687
        $fileHandle = fopen('local-info.'.$ext, "w");
688
        if ($fileHandle === FALSE) {
689
            throw new Exception("problem opening the file");
690
        }
691
        fwrite($fileHandle, $blob);
692
        fclose($fileHandle);
693
        return(['name' => 'local-info.'.$ext, 'mime' => $ext]);
694
    }
695
696
    /**
697
     * returns the attributes of the profile for which to generate an installer
698
     * 
699
     * In condensed notion, and most specific level only (i.e. ignores overridden attributes from a higher level)
700
     * @param \core\AbstractProfile $profile the Profile in question
701
     * @return array
702
     */
703
    private function getProfileAttributes(AbstractProfile $profile)
704
    {
705
        $bestMatchEap = $this->selectedEap;
706
        if (count($bestMatchEap) > 0) {
707
            $a = $profile->getCollapsedAttributes($bestMatchEap);
708
            $a['eap'] = $bestMatchEap;
709
            $a['all_eaps'] = $profile->getEapMethodsinOrderOfPreference(1);
710
            return($a);
711
        }
712
        echo("No supported eap types found for this profile.\n");
713
        return [];
714
    }
715
716
    /**
717
     * dumps attributes for debugging purposes
718
     *
719
     * dumpAttributes method is supplied for debugging purposes, it simply dumps the attribute array
720
     * to a file with name passed in the attribute.
721
     * @param string $file the output file name
722
     * @return void
723
     */
724
    protected function dumpAttributes($file)
725
    {
726
        ob_start();
727
        print_r($this->attributes);
728
        $output = ob_get_clean();
729
        file_put_contents($file, $output);
730
    }
731
732
    /**
733
     * placeholder for the main device method
734
     * @return string
735
     */
736
    abstract public function writeInstaller();
737
738
    /**
739
     * collates the string to use as EAP outer ID
740
     * 
741
     * @return string|NULL
742
     */
743
    protected function determineOuterIdString()
744
    {
745
        $outerId = NULL;
746
        if (isset($this->attributes['internal:use_anon_outer']) && $this->attributes['internal:use_anon_outer'][0] == "1" && isset($this->attributes['internal:realm'])) {
747
            $outerId = "@".$this->attributes['internal:realm'][0];
748
            if (isset($this->attributes['internal:anon_local_value'])) {
749
                $outerId = $this->attributes['internal:anon_local_value'][0].$outerId;
750
            }
751
        }
752
        return $outerId;
753
    }
754
755
    /**
756
     * Array passing all options to the device module.
757
     *
758
     * $attributes array contains option values defined for the institution and a particular
759
     * profile (possibly overriding one another) ready for the device module to consume.
760
     * 
761
     * For each of the options the value is another array of vales (even if only one value is present).
762
     * Some attributes may be missing if they have not been configured for a viven institution or profile.
763
     *
764
     * The following attributes are meant to be used by device modules:
765
     * - <b>general:geo_coordinates</b> -  geographical coordinates of the institution or a campus
766
     * - <b>support:info_file</b>  -  consent file displayed to the users                                                         
767
     * - <b>general:logo_file</b>  -  file data containing institution logo                                                      
768
     * - <b>support:eap_types</b>  -  URL to a local support page for a specific eap methiod, not to be confused with general:url 
769
     * - <b>support:email</b>      -  email for users to contact for local instructions                                           
770
     * - <b>support:phone</b>      -  telephone number for users to contact for local instructions                                
771
     * - <b>support:url</b>        -  URL where the user will find local instructions       
772
     * - <b>internal:info_file</b> -  the pathname of the info_file saved in the working directory
773
     * - <b>internal:logo_file</b>  -  array of pathnames of logo_files saved in the working directory
774
     * - <b>internal:CAs</b> - the value is an array produced by X509::processCertificate() with the following filds
775
     * - <b>internal:consortia</b> an array of consortion IO as declared in the Confassistant config
776
     * - <b>internal:networks</b> - an array of network parameters  as declared in the Confassistant config
777
     * - <b>internal:profile_count</b> - the number of profiles for the associated IdP
778
     *
779
     *
780
     * these attributes are available and can be used, but the "internal" attributes are better suited for modules
781
     * -  eap:ca_file    -      certificate of the CA signing the RADIUS server key                                         
782
     * - <b>media:SSID</b>       -  additional SSID to configure, WPA2/AES only (device modules should use internal:networks)
783
     *
784
     * @var array $attributes
785
     * @see \core\common\X509::processCertificate()
786
     * 
787
     */
788
    public $attributes;
789
790
    /**
791
     * stores the path to the module source location and is used 
792
     * by copyFile and translateFile
793
     * the only reason for it to be a public variable ies that it is set by the DeviceFactory class
794
     * module_path should not be used by module drivers.
795
     * 
796
     * @var string 
797
     */
798
    public $module_path;
799
800
    /**
801
     * The optimal EAP type selected given profile and device
802
     * 
803
     * @var array
804
     */
805
    public $selectedEap;
806
807
    /**
808
     * The optimal EAP type selected given profile and device, as object
809
     * 
810
     * @var \core\common\EAP
811
     */
812
    public $selectedEapObject;
813
814
    /**
815
     * the full path to the profile signing program
816
     * device modules which require signing should use this property to exec the signer
817
     * the signer program must accept two arguments - input and output file names
818
     * the signer program mus operate in the local directory and filenames are relative to this
819
     * directory
820
     *
821
     * @var string
822
     */
823
    public $sign;
824
825
    /**
826
     * the name of the signer program (without full path)
827
     * 
828
     * @var string
829
     */
830
    public $signer;
831
832
    /**
833
     * The string identifier of the device (don't show this to users)
834
     * 
835
     * @var string
836
     */
837
    public $device_id;
838
839
    /**
840
     * See devices-template.php for a list of available options
841
     * 
842
     * @var array
843
     */
844
    public $options;
845
846
    /**
847
     * This string will be shown if no support email was configured by the admin
848
     * 
849
     * @var string 
850
     */
851
    public $support_email_substitute;
852
853
    /**
854
     * This string will be shown if no support URL was configured by the admin
855
     * 
856
     * @var string 
857
     */
858
    public $support_url_substitute;
859
860
    /**
861
     * This string should be used by all installer modules to set the 
862
     * installer file basename.
863
     *
864
     * @var string 
865
     */
866
    public $installerBasename;
867
868
    /**
869
     * stores the PKCS#12 DER representation of a client certificate for 
870
     * SilverBullet along with some metadata in an array
871
     * 
872
     * @var array
873
     */
874
    protected $clientCert = [];
875
876
    /**
877
     * stores identifier used by GEANTLink profiles
878
     * 
879
     * @var string
880
     */
881
    public $deviceUUID;
882
}
883