Passed
Push — master ( 2fc2c9...83b4cf )
by Tomasz
07:48
created

DeviceConfig::getInstallerBasename()   B

Complexity

Conditions 8
Paths 13

Size

Total Lines 31
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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