Passed
Push — master ( 17bffe...10bee5 )
by Stefan
06:13
created

DeviceConfig   F

Complexity

Total Complexity 91

Size/Duplication

Total Lines 751
Duplicated Lines 5.99 %

Importance

Changes 0
Metric Value
wmc 91
dl 45
loc 751
rs 1.263
c 0
b 0
f 0

21 Methods

Rating   Name   Duplication   Size   Complexity  
A writeDeviceInfo() 0 2 1
A calculatePreferredEapType() 0 9 4
A copyFile() 0 15 4
A writeInstaller() 0 2 1
C saveCertificateFiles() 0 33 7
B translateString() 0 19 5
A setSupportedEapMethods() 0 4 1
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 __construct() 17 17 2
A findSourceFile() 0 8 3
A getProfileAttributes() 0 10 2
D setup() 8 82 14
A uuid() 0 14 2
A saveInfoFile() 0 12 3
C getSSIDs() 19 34 11
C translateFile() 0 32 7
A dumpAttibutes() 0 5 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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, true);
0 ignored issues
show
Unused Code introduced by
The call to core\common\Logging::debug() has too many arguments starting with true. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

87
        $this->loggerInstance->/** @scrutinizer ignore-call */ 
88
                               debug(4, $this->supportedEapMethods, true);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
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 View Code Duplication
    public function __construct() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 == "") {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
107
            // oh well.
108
        }
109
110
        $this->nomenclature_fed = _(CONFIG_CONFASSISTANT['CONSORTIUM']['nomenclature_federation']);
111
        $this->nomenclature_inst = _(CONFIG_CONFASSISTANT['CONSORTIUM']['nomenclature_institution']);
112
    }
113
114
    /**
115
     * generates a UUID, for the devices which identify file contents by UUID
116
     *
117
     * @param string $prefix an extra prefix to set before the UUID
118
     * @return string UUID (possibly prefixed)
119
     */
120
    public function uuid($prefix = '', $deterministicSource = NULL) {
121
        if ($deterministicSource === NULL) {
122
            $chars = md5(uniqid(mt_rand(), true));
123
        } else {
124
            $chars = md5($deterministicSource);
125
        }
126
        // these substr() are guaranteed to yield actual string data, as the
127
        // base string is an MD5 hash - has sufficient length
128
        $uuid = /** @scrutinizer ignore-type */ substr($chars, 0, 8) . '-';
129
        $uuid .= /** @scrutinizer ignore-type */ substr($chars, 8, 4) . '-';
130
        $uuid .= /** @scrutinizer ignore-type */ substr($chars, 12, 4) . '-';
131
        $uuid .= /** @scrutinizer ignore-type */ substr($chars, 16, 4) . '-';
132
        $uuid .= /** @scrutinizer ignore-type */ substr($chars, 20, 12);
133
        return $prefix . $uuid;
134
    }
135
136
    /**
137
     * Set up working environment for a device module
138
     *
139
     * Sets up the device module environment taking into account the actual profile
140
     * selected by the user in the GUI. The selected profile is passed as the
141
     * Profile $profile argumant.
142
     *
143
     * This method needs to be called after the device instance has been created (the GUI class does that)
144
     *
145
     * setup performs the following tasks:
146
     * - collect profile attributes and pass them as the attributes property;
147
     * - create the temporary working directory
148
     * - process CA certificates and store them as 'internal:CAs' attribute
149
     * - process and save optional info files and store references to them in
150
     *   'internal:info_file' attribute
151
     * @param AbstractProfile $profile the profile object which will be passed by the caller
152
     * @final not to be redefined
153
     */
154
    final public function setup(AbstractProfile $profile, $token = NULL, $importPassword = NULL) {
155
        $this->loggerInstance->debug(4, "module setup start\n");
156
        $purpose = 'installer';
157
        if (!$profile instanceof AbstractProfile) {
158
            $this->loggerInstance->debug(2, "No profile has been set\n");
159
            throw new Exception("No profile has been set");
160
        }
161
162
        $eaps = $profile->getEapMethodsinOrderOfPreference(1);
163
        $this->calculatePreferredEapType($eaps);
164
        if (count($this->selectedEap) == 0) {
165
            throw new Exception("No EAP type specified.");
166
        }
167
        $this->attributes = $this->getProfileAttributes($profile);
168
        $this->deviceUUID = $this->uuid('', 'CAT' . $profile->institution . "-" . $profile->identifier . "-" . $this->device_id);
169
170
171
        // if we are instantiating a Silverbullet profile AND have been given
172
        // a token, attempt to create the client certificate NOW
173
        // then, this is the only instance of the device ever which knows the
174
        // cert and private key. It's not saved anywhere, so it's gone forever
175
        // after code execution!
176
177
        $this->loggerInstance->debug(5, "DeviceConfig->setup() - preliminaries done.\n");
178
        if ($profile instanceof ProfileSilverbullet && $token !== NULL && $importPassword !== NULL) {
179
            $this->clientCert = $profile->issueCertificate($token, $importPassword);
180
            // add a UUID identifier for the devices that want one
181
            $this->clientCert['GUID'] = $this->uuid("", $this->clientCert['certdata']);
182
            // we need to drag this along; ChromeOS needs it outside the P12 container to encrypt the entire *config* with it.
183
            // Because encrypted private keys are not supported as per spec!
184
            $purpose = 'silverbullet';
185
            // let's keep a record for which device type this token was consumed
186
            $dbInstance = DBConnection::handle("INST");
187
            $devicename = \devices\Devices::listDevices()[$this->device_id]['display'];
188
            $certId = $this->clientCert['certificateId'];
189
            $dbInstance->exec("UPDATE `silverbullet_certificate` SET `device` = ? WHERE `id` = ?", "si", $devicename, $certId);    
190
        }
191
        $this->loggerInstance->debug(5, "DeviceConfig->setup() - silverbullet checks done.\n");
192
        // create temporary directory, its full path will be saved in $this->FPATH;
193
        $tempDir = $this->createTemporaryDirectory($purpose);
194
        $this->FPATH = $tempDir['dir'];
195
        mkdir($tempDir['dir'] . '/tmp');
196
        chdir($tempDir['dir'] . '/tmp');
197
        $caList = [];
198
        $x509 = new \core\common\X509();
199
        if (isset($this->attributes['eap:ca_file'])) {
200
            foreach ($this->attributes['eap:ca_file'] as $ca) {
201
                $processedCert = $x509->processCertificate($ca);
202
                if (is_array($processedCert)) {
203
                    // add a UUID for convenience (some devices refer to their CAs by a UUID value)
204
                    $processedCert['uuid'] = $this->uuid("", $processedCert['pem']);
205
                    $caList[] = $processedCert;
206
                }
207
            }
208
            $this->attributes['internal:CAs'][0] = $caList;
209
        }
210
211
        if (isset($this->attributes['support:info_file'])) {
212
            $this->attributes['internal:info_file'][0] = $this->saveInfoFile($this->attributes['support:info_file'][0]);
213
        }
214 View Code Duplication
        if (isset($this->attributes['general:logo_file'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
215
            $this->loggerInstance->debug(5, "saving IDP logo\n");
216
            $this->attributes['internal:logo_file'] = $this->saveLogoFile($this->attributes['general:logo_file'],'idp');
217
        }
218 View Code Duplication
        if (isset($this->attributes['fed:logo_file'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
219
            $this->loggerInstance->debug(5, "saving FED logo\n");
220
            $this->attributes['fed:logo_file'] = $this->saveLogoFile($this->attributes['fed:logo_file'], 'fed');
221
        }
222
        $this->attributes['internal:SSID'] = $this->getSSIDs()['add'];
223
224
        $this->attributes['internal:remove_SSID'] = $this->getSSIDs()['del'];
225
226
        $this->attributes['internal:consortia'] = $this->getConsortia();
227
        $olddomain = $this->languageInstance->setTextDomain("core");
228
        $this->support_email_substitute = sprintf(_("your local %s support"), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name']);
229
        $this->support_url_substitute = sprintf(_("your local %s support page"), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name']);
230
        $this->languageInstance->setTextDomain($olddomain);
231
232
        if ($this->signer && $this->options['sign']) {
233
            $this->sign = ROOT . '/signer/' . $this->signer;
234
        }
235
        $this->installerBasename = $this->getInstallerBasename();
236
    }
237
238
    /**
239
     * Selects the preferred eap method based on profile EAP configuration and device EAP capabilities
240
     *
241
     * @param array $eapArrayofObjects an array of eap methods supported by a given device
242
     */
243
    public function calculatePreferredEapType($eapArrayofObjects) {
244
        $this->selectedEap = [];
245
        foreach ($eapArrayofObjects as $eap) {
246
            if (in_array($eap->getArrayRep(), $this->supportedEapMethods)) {
247
                $this->selectedEap = $eap->getArrayRep();
248
            }
249
        }
250
        if ($this->selectedEap != []) {
251
            $this->selectedEapObject = new common\EAP($this->selectedEap);
252
        }
253
    }
254
255
    /**
256
     * prepare usage information for the installer
257
     * every device module should override this method
258
     *
259
     * @return String HTML text to be displayed
260
     */
261
    public function writeDeviceInfo() {
262
        return _("Sorry, this should not happen - no additional information is available");
263
    }
264
    
265
    /**
266
     * some modules have a complex directory structure. This helper finds resources
267
     * in that structure. Mostly used in the Windows modules.
268
     * 
269
     * @param string $file the filename to search for (without path)
270
     * @return string|false the filename as found, with path, or FALSE if it does not exist
271
     */
272
    private function findSourceFile($file) {
273
        if (is_file($this->module_path . '/Files/' . $this->device_id . '/' . $file)) {
274
            return $this->module_path . '/Files/' . $this->device_id . '/' . $file;
275
        } elseif (is_file($this->module_path . '/Files/' . $file)) {
276
            return $this->module_path . '/Files/' . $file;
277
        } else {
278
            $this->loggerInstance->debug(2, "requested file $file does not exist\n");
279
            return(FALSE);
280
        }
281
    }
282
283
    /**
284
     *  Copy a file from the module location to the temporary directory.
285
     *
286
     * If the second argument is provided then the file will be saved under the name 
287
     * taken form this argument. If only one parameter is given, source and destination
288
     * filenames are the same
289
     * Source file can be located either in the Files subdirectory or in the sibdirectory of Files
290
     * named the same as device_id. The second option takes precedence.
291
     *
292
     * @param string $source_name The source file name
293
     * @param string $output_name The destination file name
294
     *
295
     * @return bool result of the copy operation
296
     * @final not to be redefined
297
     */
298
    final protected function copyFile($source_name, $output_name = NULL) {
299
        if ($output_name === NULL) {
300
            $output_name = $source_name;
301
        }
302
        $this->loggerInstance->debug(5, "fileCopy($source_name, $output_name)\n");
303
        $source = $this->findSourceFile($source_name);
304
        if ($source === FALSE) {
305
            return FALSE;
306
        }
307
        $this->loggerInstance->debug(5, "Copying $source to $output_name\n");
308
        $result = copy($source, "$output_name");
309
        if (!$result) {
310
            $this->loggerInstance->debug(2, "fileCopy($source_name, $output_name) failed\n");
311
        }
312
        return($result);
313
    }
314
315
    /**
316
     *  Copy a file from the module location to the temporary directory aplying transcoding.
317
     *
318
     * Transcoding is only required for Windows installers, and no Unicode support
319
     * in NSIS (NSIS version below 3)
320
     * Trancoding is only applied if the third optional parameter is set and nonzero
321
     * If CONFIG['NSIS']_VERSION is set to 3 or more, no transcoding will be applied
322
     * regardless of the third parameter value.
323
     * If the second argument is provided and is not equal to 0, then the file will be
324
     * saved under the name taken from this argument.
325
     * If only one parameter is given or the second is equal to 0, source and destination
326
     * filenames are the same.
327
     * The third optional parameter, if nonzero, should be the character set understood by iconv
328
     * This is required by the Windows installer and is expected to go away in the future.
329
     * Source file can be located either in the Files subdirectory or in the sibdirectory of Files
330
     * named the same as device_id. The second option takes precedence.
331
     *
332
     * @param string $source_name The source file name
333
     * @param string $output_name The destination file name
334
     * @param int $encoding Set Windows charset if non-zero
335
     *
336
     * @final not to be redefined
337
     */
338
    final protected function translateFile($source_name, $output_name = NULL, $encoding = 0) {
339
        if (CONFIG_CONFASSISTANT['NSIS_VERSION'] >= 3) {
340
            $encoding = 0;
341
        }
342
        if ($output_name === NULL) {
343
            $output_name = $source_name;
344
        }
345
346
        $this->loggerInstance->debug(5, "translateFile($source_name, $output_name, $encoding)\n");
347
        ob_start();
348
        $this->loggerInstance->debug(5, $this->module_path . '/Files/' . $this->device_id . '/' . $source_name . "\n");
349
        $source = $this->findSourceFile($source_name);
350
        
351
        if ($source !== FALSE) { // if there is no file found, don't attempt to include an uninitialised variable
352
            include($source);
353
        }
354
        $output = ob_get_clean();
355
        if ($encoding) {
356
            $outputClean = iconv('UTF-8', $encoding . '//TRANSLIT', $output);
357
            if ($outputClean) {
358
                $output = $outputClean;
359
            }
360
        }
361
        $fileHandle = fopen("$output_name", "w");
362
        if (!$fileHandle) {
363
            $this->loggerInstance->debug(2, "translateFile($source, $output_name, $encoding) failed\n");
364
            return FALSE;
365
        }
366
        fwrite($fileHandle, $output);
367
        fclose($fileHandle);
368
        $this->loggerInstance->debug(5, "translateFile($source, $output_name, $encoding) end\n");
369
        return TRUE;
370
    }
371
372
    /**
373
     * Transcode a string adding double quotes escaping
374
     *
375
     * Transcoding is only required for Windows installers, and no Unicode support
376
     * in NSIS (NSIS version below 3)
377
     * Trancoding is only applied if the third optional parameter is set and nonzero
378
     * If CONFIG['NSIS']_VERSION is set to 3 or more, no transcoding will be applied
379
     * regardless of the second parameter value.
380
     * The second optional parameter, if nonzero, should be the character set understood by iconv
381
     * This is required by the Windows installer and is expected to go away in the future.
382
     *
383
     * @param string $source_string The source string
384
     * @param int $encoding Set Windows charset if non-zero
385
     *
386
     * @final not to be redefined
387
     */
388
    final protected function translateString($source_string, $encoding = 0) {
389
        $this->loggerInstance->debug(5, "translateString input: \"$source_string\"\n");
390
        if (empty($source_string)) {
391
            return($source_string);
392
        }
393
        if (CONFIG_CONFASSISTANT['NSIS_VERSION'] >= 3) {
394
            $encoding = 0;
395
        }
396
        if ($encoding) {
397
            $output_c = iconv('UTF-8', $encoding . '//TRANSLIT', $source_string);
398
        } else {
399
            $output_c = $source_string;
400
        }
401
        if ($output_c) {
402
            $source_string = str_replace('"', '$\\"', $output_c);
403
        } else {
404
            $this->loggerInstance->debug(2, "Failed to convert string \"$source_string\"\n");
405
        }
406
        return $source_string;
407
    }
408
409
    /**
410
     * Save certificate files in either DER or PEM format
411
     *
412
     * Certificate files will be saved in the module working directory.
413
     * @param string $format  only "der" and "pem" are currently allowed
414
     * @return array an array of arrays or FALSE on error
415
     * saved certificate file names are avalable under the 'file' index
416
     * additional array entries are indexed as 'sha1', 'md5', and 'root'.
417
     * sha1 and md5 are correcponding certificate hashes
418
     * root is set to 1 for the CA roor certicicate and 0 otherwise
419
     */
420
    final protected function saveCertificateFiles($format) {
421
        switch ($format) {
422
            case "der": // fall-thorugh, same treatment
423
            case "pem":
424
                $iterator = 0;
425
                $caFiles = [];
426
                $caArray = $this->attributes['internal:CAs'][0];
427
                if (!$caArray) {
428
                    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...
429
                }
430
                foreach ($caArray as $certAuthority) {
431
                    $fileHandle = fopen("cert-$iterator.crt", "w");
432
                    if (!$fileHandle) {
433
                        throw new Exception("problem opening the file");
434
                    }
435
                    if ($format === "pem") {
436
                        fwrite($fileHandle, $certAuthority['pem']);
437
                    } else {
438
                        fwrite($fileHandle, $certAuthority['der']);
439
                    }
440
                    fclose($fileHandle);
441
                    $certAuthorityProps = [];
442
                    $certAuthorityProps['file'] = "cert-$iterator.crt";
443
                    $certAuthorityProps['sha1'] = $certAuthority['sha1'];
444
                    $certAuthorityProps['md5'] = $certAuthority['md5'];
445
                    $certAuthorityProps['root'] = $certAuthority['root'];
446
                    $caFiles[] = $certAuthorityProps;
447
                    $iterator++;
448
                }
449
                return($caFiles);
450
            default:
451
                $this->loggerInstance->debug(2, 'incorrect format value specified');
452
                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...
453
        }
454
    }
455
456
    /**
457
     * Generate installer filename base.
458
     * Device module should use this name adding an extension.
459
     * Normally the device identifier follows the Consortium name.
460
     * The sting taken for the device identifier equals (by default) to the index in the listDevices array,
461
     * but can be overriden with the 'device_id' device option.
462
     */
463
    private function getInstallerBasename() {
464
        $replace_pattern = '/[ ()\/\'"]+/';
465
        $consortiumName = iconv("UTF-8", "US-ASCII//TRANSLIT", preg_replace($replace_pattern, '_', CONFIG_CONFASSISTANT['CONSORTIUM']['name']));
466
        if (isset($this->attributes['profile:customsuffix'][1])) { 
467
            // this string will end up as a filename on a filesystem, so always
468
            // take a latin-based language variant if available
469
            // and then scrub non-ASCII just in case
470
            return $consortiumName . "-" . $this->getDeviceId() . iconv("UTF-8", "US-ASCII//TRANSLIT", preg_replace($replace_pattern, '_', $this->attributes['profile:customsuffix'][1]));
471
        }
472
        // Okay, no custom suffix. 
473
        // Use the configured inst name and apply shortening heuristics
474
        $lang_pointer = CONFIG['LANGUAGES'][$this->languageInstance->getLang()]['latin_based'] == TRUE ? 0 : 1;
475
        $this->loggerInstance->debug(5, "getInstallerBasename1:" . $this->attributes['general:instname'][$lang_pointer] . "\n");
476
        $inst = iconv("UTF-8", "US-ASCII//TRANSLIT", preg_replace($replace_pattern, '_', $this->attributes['general:instname'][$lang_pointer]));
477
        $this->loggerInstance->debug(4, "getInstallerBasename2:$inst\n");
478
        $Inst_a = explode('_', $inst);
479
        if (count($Inst_a) > 2) {
480
            $inst = '';
481
            foreach ($Inst_a as $i) {
482
                $inst .= $i[0];
483
            }
484
        }
485
        // and if the inst has multiple profiles, add the profile name behin
486
        if ($this->attributes['internal:profile_count'][0] > 1) {
487
            if (!empty($this->attributes['profile:name']) && !empty($this->attributes['profile:name'][$lang_pointer])) {
488
                $profTemp = iconv("UTF-8", "US-ASCII//TRANSLIT", preg_replace($replace_pattern, '_', $this->attributes['profile:name'][$lang_pointer]));
489
                $prof = preg_replace('/_+$/', '', $profTemp);
490
                return $consortiumName . '-' . $this->getDeviceId() . $inst . '-' . $prof;
491
            }
492
        }
493
        return $consortiumName . '-' . $this->getDeviceId() . $inst;
494
    }
495
496
    /**
497
     * returns the device_id of the current device
498
     * 
499
     * @return string
500
     */
501
    private function getDeviceId() {
502
        $deviceId = $this->device_id;
503
        if (isset($this->options['device_id'])) {
504
            $deviceId = $this->options['device_id'];
505
        }
506
        if ($deviceId !== '') {
507
            $deviceId .= '-';
508
        }
509
        return $deviceId;
510
    }
511
512
    /**
513
     * returns the list of SSIDs that installers should treat. 
514
     * 
515
     * Includes both SSIDs to be set up (and whether it's a TKIP-mixed or AES-only SSID) and SSIDs to be deleted
516
     * 
517
     * @return array
518
     */
519
    private function getSSIDs() {
520
        $ssidList = [];
521
        $ssidList['add'] = [];
522
        $ssidList['del'] = [];
523
        if (isset(CONFIG_CONFASSISTANT['CONSORTIUM']['ssid'])) {
524
            foreach (CONFIG_CONFASSISTANT['CONSORTIUM']['ssid'] as $ssid) {
525
                if (isset(CONFIG_CONFASSISTANT['CONSORTIUM']['tkipsupport']) && CONFIG_CONFASSISTANT['CONSORTIUM']['tkipsupport'] == TRUE) {
526
                    $ssidList['add'][$ssid] = 'TKIP';
527
                } else {
528
                    $ssidList['add'][$ssid] = 'AES';
529
                    $ssidList['del'][$ssid] = 'TKIP';
530
                }
531
            }
532
        }
533 View Code Duplication
        if (isset($this->attributes['media:SSID'])) {
534
            $ssidWpa2 = $this->attributes['media:SSID'];
535
536
            foreach ($ssidWpa2 as $ssid) {
537
                $ssidList['add'][$ssid] = 'AES';
538
            }
539
        }
540 View Code Duplication
        if (isset($this->attributes['media:SSID_with_legacy'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
541
            $ssidTkip = $this->attributes['media:SSID_with_legacy'];
542
            foreach ($ssidTkip as $ssid) {
543
                $ssidList['add'][$ssid] = 'TKIP';
544
            }
545
        }
546 View Code Duplication
        if (isset($this->attributes['media:remove_SSID'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
547
            $ssidRemove = $this->attributes['media:remove_SSID'];
548
            foreach ($ssidRemove as $ssid) {
549
                $ssidList['del'][$ssid] = 'DEL';
550
            }
551
        }
552
        return $ssidList;
553
    }
554
555
    /**
556
     * returns the list of Hotspot 2.0 / Passpoint roaming consortia to set up
557
     * 
558
     * @return array
559
     */
560
    private function getConsortia() {
561
        $consortia = CONFIG_CONFASSISTANT['CONSORTIUM']['interworking-consortium-oi'];
562
        if (isset($this->attributes['media:consortium_OI'])) {
563
            foreach ($this->attributes['media:consortium_OI'] as $new_oi) {
564
                $consortia[] = $new_oi;
565
            }
566
        }
567
        return $consortia;
568
    }
569
570
    /**
571
     * An array with shorthand definitions for MIME types
572
     * @var array
573
     */
574
    private $mime_extensions = [
575
        'text/plain' => 'txt',
576
        'text/rtf' => 'rtf',
577
        'application/pdf' => 'pdf',
578
    ];
579
580
    /**
581
     * saves a number of logos to a cache directory on disk.
582
     * 
583
     * @param array $logos list of logos (binary strings each)
584
     * @param string $type a qualifier what type of logo this is
585
     * @return array list of filenames and the mime types
586
     * @throws Exception
587
     */
588
    private function saveLogoFile($logos,$type) {
589
        $iterator = 0;
590
        $returnarray = [];
591
        foreach ($logos as $blob) {
592
            $finfo = new \finfo(FILEINFO_MIME_TYPE);
593
            $mime = $finfo->buffer($blob);
594
            $matches = [];
595
            if (preg_match('/^image\/(.*)/', $mime, $matches)) {
596
                $ext = $matches[1];
597
            } else {
598
                $ext = 'unsupported';
599
            }
600
            $this->loggerInstance->debug(5, "saveLogoFile: $mime : $ext\n");
601
            $fileName = 'logo-' . $type . $iterator . '.' . $ext;
602
            $fileHandle = fopen($fileName, "w");
603
            if (!$fileHandle) {
604
                $this->loggerInstance->debug(2, "saveLogoFile failed for: $fileName\n");
605
                throw new Exception("problem opening the file");
606
            }
607
            fwrite($fileHandle, $blob);
608
            fclose($fileHandle);
609
            $returnarray[] = ['name' => $fileName, 'mime' => $ext];
610
            $iterator++;
611
        }
612
        return($returnarray);
613
    }
614
615
    /**
616
     * saves the Terms of Use file onto disk
617
     * 
618
     * @param string $blob the Terms of Use
619
     * @return array with one entry, containging the filename and mime type
620
     * @throws Exception
621
     */
622
    private function saveInfoFile($blob) {
623
        $finfo = new \finfo(FILEINFO_MIME_TYPE);
624
        $mime = $finfo->buffer($blob);
625
        $ext = isset($this->mime_extensions[$mime]) ? $this->mime_extensions[$mime] : 'usupported';
626
        $this->loggerInstance->debug(5, "saveInfoFile: $mime : $ext\n");
627
        $fileHandle = fopen('local-info.' . $ext, "w");
628
        if (!$fileHandle) {
629
            throw new Exception("problem opening the file");
630
        }
631
        fwrite($fileHandle, $blob);
632
        fclose($fileHandle);
633
        return(['name' => 'local-info.' . $ext, 'mime' => $ext]);
634
    }
635
636
    /**
637
     * returns the attributes of the profile for which to generate an installer
638
     * 
639
     * In condensed notion, and most specific level only (i.e. ignores overriden attributes from a higher level)
640
     * @param \core\AbstractProfile $profile
641
     * @return array
642
     */
643
    private function getProfileAttributes(AbstractProfile $profile) {
644
        $bestMatchEap = $this->selectedEap;
645
        if (count($bestMatchEap) > 0) {
646
            $a = $profile->getCollapsedAttributes($bestMatchEap);
647
            $a['eap'] = $bestMatchEap;
648
            $a['all_eaps'] = $profile->getEapMethodsinOrderOfPreference(1);
649
            return($a);
650
        }
651
        print("No supported eap types found for this profile.\n");
652
        return [];
653
    }
654
655
    /**
656
     * dumps attributes for debugging purposes
657
     *
658
     * dumpAttibutes method is supplied for debuging purposes, it simply dumps the attribute array
659
     * to a file with name passed in the attribute.
660
     * @param string $file the output file name
661
     */
662
    protected function dumpAttibutes($file) {
663
        ob_start();
664
        print_r($this->attributes);
665
        $output = ob_get_clean();
666
        file_put_contents($file, $output);
667
    }
668
669
    /**
670
     * placeholder for the main device method
671
     * @return string
672
     */
673
    protected function writeInstaller() {
674
        return("download path");
675
    }
676
677
    /**
678
     * collates the string to use as EAP outer ID
679
     * 
680
     * @return string
681
     */
682
    protected function determineOuterIdString() {
683
        $outerId = 0;
684
        if (isset($this->attributes['internal:use_anon_outer']) && $this->attributes['internal:use_anon_outer'][0] == "1" && isset($this->attributes['internal:realm'])) {
685
            $outerId = "@" . $this->attributes['internal:realm'][0];
686
            if (isset($this->attributes['internal:anon_local_value'])) {
687
                $outerId = $this->attributes['internal:anon_local_value'][0] . $outerId;
688
            }
689
        }
690
        return $outerId;
691
    }
692
693
    /**
694
     * Array passing all options to the device module.
695
     *
696
     * $attrbutes array contains option values defined for the institution and a particular
697
     * profile (possibly overriding one another) ready for the device module to consume.
698
     * 
699
     * For each of the options the value is another array of vales (even if only one value is present).
700
     * Some attributes may be missing if they have not been configured for a viven institution or profile.
701
     *
702
     * The following attributes are meant to be used by device modules:
703
     * - <b>general:geo_coordinates</b> -  geographical coordinates of the institution or a campus
704
     * - <b>support:info_file</b>  -  consent file displayed to the users                                                         
705
     * - <b>general:logo_file</b>  -  file data containing institution logo                                                      
706
     * - <b>support:eap_types</b>  -  URL to a local support page for a specific eap methiod, not to be confused with general:url 
707
     * - <b>support:email</b>      -  email for users to contact for local instructions                                           
708
     * - <b>support:phone</b>      -  telephone number for users to contact for local instructions                                
709
     * - <b>support:url</b>        -  URL where the user will find local instructions       
710
     * - <b>internal:info_file</b> -  the pathname of the info_file saved in the working directory
711
     * - <b>internal:logo_file</b>  -  array of pathnames of logo_files saved in the working directory
712
     * - <b>internal:CAs</b> - the value is an array produced by X509::processCertificate() with the following filds
713
     * - <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.
714
     * -<b>internal:profile_count</b> - the number of profiles for the associated IdP
715
     *
716
     *
717
     * these attributes are available and can be used, but the "internal" attributes are better suited for modules
718
     * -  eap:ca_file    -      certificate of the CA signing the RADIUS server key                                         
719
     * - <b>media:SSID</b>       -  additional SSID to configure, WPA2/AES only (device modules should use internal:SSID)
720
     * - <b>media:SSID_with_legacy</b> -  additional SSID to configure, WPA2/AES and WPA/TKIP (device modules should use internal:SSID)
721
     *
722
     * @see \core\common\X509::processCertificate()
723
     * @var array $attributes
724
     */
725
    public $attributes;
726
727
    /**
728
     * stores the path to the module source location and is used 
729
     * by copyFile and translateFile
730
     * the only reason for it to be a public variable ies that it is set by the DeviceFactory class
731
     * module_path should not be used by module drivers.
732
     * @var string 
733
     */
734
    public $module_path;
735
736
    /**
737
     * * The optimal EAP type selected given profile and device
738
     * @var array
739
     */
740
    public $selectedEap;
741
    public $selectedEapObject;
742
743
    /**
744
     * the path to the profile signing program
745
     * device modules which require signing should use this property to exec the signer
746
     * the signer program must accept two arguments - input and output file names
747
     * the signer program mus operate in the local directory and filenames are relative to this
748
     * directory
749
     *
750
     * @var string
751
     */
752
    public $sign;
753
    public $signer;
754
755
    /**
756
     * The string identifier of the device (don't show this to users)
757
     * @var string
758
     */
759
    public $device_id;
760
761
    /**
762
     * See devices-template.php for a list of available options
763
     * @var array
764
     */
765
    public $options;
766
767
    /**
768
     * This string will be shown if no support email was configured by the admin
769
     * 
770
     * @var string 
771
     */
772
    public $support_email_substitute;
773
774
    /**
775
     * This string will be shown if no support URL was configured by the admin
776
     * 
777
     * @var string 
778
     */
779
    public $support_url_substitute;
780
781
    /**
782
     * This string should be used by all installer modules to set the 
783
     * installer file basename.
784
     *
785
     * @var string 
786
     */
787
    public $installerBasename;
788
789
    /**
790
     * stores the PKCS#12 DER representation of a client certificate for SilverBullet
791
     */
792
    protected $clientCert;
793
794
    /**
795
     * stores identifier used by GEANTLink profiles
796
     */
797
    public $deviceUUID;
798
799
}
800