Test Failed
Push — master ( 60b361...3a07f6 )
by Stefan
10:13
created

DeviceConfig::getProfileAttributes()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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