Test Setup Failed
Push — master ( 9b5eaa...4984c7 )
by Tomasz
16:58
created

DeviceConfig::setup()   C

Complexity

Conditions 15
Paths 129

Size

Total Lines 82
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
eloc 51
c 3
b 0
f 1
dl 0
loc 82
rs 5.675
cc 15
nc 129
nop 4

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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