Passed
Push — master ( 5c87ed...e4bb53 )
by Stefan
04:05
created

DeviceConfig   F

Complexity

Total Complexity 89

Size/Duplication

Total Lines 750
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 89
dl 0
loc 750
rs 1.263
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A setSupportedEapMethods() 0 4 1
A __construct() 0 18 2
A copyFile() 0 15 4
A writeDeviceInfo() 0 2 1
C saveCertificateFiles() 0 33 7
B translateString() 0 19 5
A calculatePreferredEapType() 0 9 4
A getDeviceId() 0 9 3
A getConsortia() 0 8 3
B saveLogoFile() 0 25 4
C getInstallerBasename() 0 31 8
B determineOuterIdString() 0 9 5
A findSourceFile() 0 8 3
A getProfileAttributes() 0 10 2
D setup() 0 82 14
A uuid() 0 14 2
A saveInfoFile() 0 12 3
D getSSIDs() 0 34 10
C translateFile() 0 32 7
A dumpAttibutes() 0 5 1

How to fix   Complexity   

Complex Class

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

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

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

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