Test Failed
Push — master ( 83b526...bd639c )
by Stefan
23:38
created

MobileconfigSuperclass::writeInstaller()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 70
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 39
c 0
b 0
f 0
dl 0
loc 70
rs 8.6737
cc 6
nc 12
nop 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * *****************************************************************************
5
 * Contributions to this work were made on behalf of the GÉANT project, a 
6
 * project that has received funding from the European Union’s Framework 
7
 * Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
8
 * Horizon 2020 research and innovation programme under Grant Agreements No. 
9
 * 691567 (GN4-1) and No. 731122 (GN4-2).
10
 * On behalf of the aforementioned projects, GEANT Association is the sole owner
11
 * of the copyright in all material which was developed by a member of the GÉANT
12
 * project. GÉANT Vereniging (Association) is registered with the Chamber of 
13
 * Commerce in Amsterdam with registration number 40535155 and operates in the 
14
 * UK as a branch of GÉANT Vereniging.
15
 * 
16
 * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. 
17
 * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
18
 *
19
 * License: see the web/copyright.inc.php file in the file structure or
20
 *          <base_url>/copyright.php after deploying the software
21
 */
22
23
/**
24
 * This file contains the installer for iOS devices and Apple 10.7 Lion
25
 *
26
 *
27
 * @author Stefan Winter <[email protected]>
28
 * @package Developer
29
 */
30
31
namespace devices\apple_mobileconfig;
32
33
use \Exception;
0 ignored issues
show
Bug introduced by
The type \Exception was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
34
35
/**
36
 * This is the main implementation class of the module
37
 *
38
 * The class should only define one public method: writeInstaller.
39
 *
40
 * All other methods and properties should be private. This example sets zipInstaller method to protected, so that it can be seen in the documentation.
41
 *
42
 * @package Developer
43
 */
44
abstract class MobileconfigSuperclass extends \core\DeviceConfig
45
{
46
47
    /**
48
     * institution name in preferred language
49
     * 
50
     * @var string
51
     */
52
    private $instName;
53
    
54
    /**
55
     * profile name in preferred language
56
     * 
57
     * @var string
58
     */
59
    private $profileName;
60
    
61
    /**
62
     * institution name, massaged to make it fit into the XML payload descriptor
63
     * 
64
     * @var string
65
     */
66
    private $massagedInst;
67
    
68
    /**
69
     * profile name, massaged to make it fit into the XML payload descriptor
70
     * 
71
     * @var string
72
     */
73
    private $massagedProfile;
74
    
75
    /**
76
     * country name, massaged to make it fit into the XML payload descriptor
77
     * 
78
     * @var string
79
     */
80
    private $massagedCountry;
81
    
82
    /**
83
     * consortium name, massaged to make it fit into the XML payload descriptor
84
     * 
85
     * @var string
86
     */
87
    private $massagedConsortium;
88
    
89
    /**
90
     * current language, without any .CHARSET suffixes
91
     * 
92
     * @var string
93
     */
94
    private $lang;
95
    
96
    /**
97
     * if we include a client certificate (silverbullet), the UUID of the certificate
98
     * 
99
     * @var string
100
     */
101
    private $clientCertUUID;
102
103
    /**
104
     * iterator to uniquely identify every Wi-Fi payload with a suffix in the file
105
     * 
106
     * @var integer
107
     */
108
    private $serial;
109
    
110
    /**
111
     * iterator to uniquely identify every removal payload with a suffix in the file
112
     * @var integer
113
     */
114
    private $removeSerial;
115
    
116
    /**
117
     * iterator to uniquely identify evera CA payload with a suffix in the file
118
     * 
119
     * @var integer
120
     */
121
    private $caSerial;
122
    
123
    /**
124
     * we need to be cautious not to include a CA twice, even if the admin
125
     * included it twice in the CAT config. This variable takes note of all
126
     * CA certs we already added to the file.
127
     * 
128
     * @var array
129
     */
130
    private $CAsAccountedFor = [];
131
    
132
    /**
133
     * we need a globally valid domain anchor, independent of the consortium
134
     */
135
    const IPHONE_PAYLOAD_PREFIX = "org.1x-config";
136
    
137
138
    /**
139
     * construct with the standard set of EAP methods we support, and preload
140
     * specialities
141
     */
142
    public function __construct()
143
    {
144
        parent::__construct();
145
        \core\common\Entity::intoThePotatoes();
146
        // that's what all variants support. Sub-classes can change it.
147
        $this->setSupportedEapMethods([\core\common\EAP::EAPTYPE_PEAP_MSCHAP2, \core\common\EAP::EAPTYPE_TTLS_PAP, \core\common\EAP::EAPTYPE_TTLS_MSCHAP2, \core\common\EAP::EAPTYPE_SILVERBULLET]);
148
        foreach(\core\common\EAP::listKnownEAPTypes() as $eapType) {
149
            if ($eapType->isPasswordRequired() || $eapType->isPasswordOptional()) {
150
                $this->specialities['internal:verify_userinput_suffix'][serialize($eapType->getArrayRep())] = _("It is not possible to actively verify the user input for suffix match; but if there is no 'Terms of Use' configured, the installer will display a corresponding hint to the user instead.");
151
                $this->specialities['media:consortium_OI'][serialize($eapType->getArrayRep())] = _("Passpoint networks are not provisioned due to severe UI limitations during install time.");
152
            }
153
        }
154
        \core\common\Entity::outOfThePotatoes();
155
    }
156
157
    /**
158
     * massage a name so that it becomes acceptable inside the plist XML
159
     * 
160
     * @param string $input the literal name
161
     * @return string
162
     */
163
    private function massageName($input)
164
    {
165
        return htmlspecialchars(strtolower(iconv("UTF-8", "US-ASCII//TRANSLIT", preg_replace(['/ /', '/\//'], '_', $input))), ENT_XML1, 'UTF-8');
166
    }
167
168
    /**
169
     * the general part of a mobileconfig file in plist format
170
     * @return string
171
     */
172
    private function generalPayload()
173
    {
174
        \core\common\Entity::intoThePotatoes();
175
        $tagline = sprintf(_("Network configuration profile '%s' of '%s' - provided by %s"), htmlspecialchars($this->profileName, ENT_XML1, 'UTF-8'), htmlspecialchars($this->instName, ENT_XML1, 'UTF-8'), \config\ConfAssistant::CONSORTIUM['display_name']);
176
177
        $eapType = $this->selectedEap;
178
        // simpler message for silverbullet
179
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
180
            $tagline = sprintf(_("%s configuration for IdP '%s' - provided by %s"), \core\ProfileSilverbullet::PRODUCTNAME, htmlspecialchars($this->instName, ENT_XML1, 'UTF-8'), \config\ConfAssistant::CONSORTIUM['display_name']);
181
        }
182
183
        $retval = "
184
      <key>PayloadDescription</key>
185
         <string>$tagline</string>
186
      <key>PayloadDisplayName</key>
187
         <string>" . \config\ConfAssistant::CONSORTIUM['display_name'] . "</string>
188
      <key>PayloadIdentifier</key>
189
         <string>" . self::IPHONE_PAYLOAD_PREFIX . ".$this->massagedConsortium.$this->massagedCountry.$this->massagedInst.$this->massagedProfile.$this->lang</string>
190
      <key>PayloadOrganization</key>
191
         <string>" . htmlspecialchars(iconv("UTF-8", "UTF-8//IGNORE", $this->attributes['general:instname'][0]), ENT_XML1, 'UTF-8') . ( $this->attributes['internal:profile_count'][0] > 1 ? " (" . htmlspecialchars(iconv("UTF-8", "UTF-8//IGNORE", $this->attributes['profile:name'][0]), ENT_XML1, 'UTF-8') . ")" : "") . "</string>
192
      <key>PayloadType</key>
193
         <string>Configuration</string>
194
      <key>PayloadUUID</key>
195
         <string>" . \core\common\Entity::uuid('', self::IPHONE_PAYLOAD_PREFIX . $this->massagedConsortium . $this->massagedCountry . $this->massagedInst . $this->massagedProfile) . "</string>
196
      <key>PayloadVersion</key>
197
         <integer>1</integer>";
198
        \core\common\Entity::outOfThePotatoes();
199
        return $retval;
200
    }
201
202
    const FILE_START = "<?xml version=\"1.0\" encoding=\"utf-8\"?>
203
<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\"
204
\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
205
<plist version=\"1.0\">
206
<dict>";
207
    const FILE_END = "</dict></plist>";
208
    const BUFFER_CONSENT_PRE = "
209
      <key>ConsentText</key>
210
         <dict>
211
            <key>default</key>
212
               <string>";
213
    const BUFFER_CONSENT_POST = "</string>
214
         </dict>
215
         ";
216
217
    /**
218
     * creates a ConsentText block if either Terms of Use are specified or the
219
     * user input hints should be displayed. Otherwise, produces nothing.
220
     * 
221
     * @return string
222
     */
223
    protected function consentBlock()
224
    {
225
        \core\common\Entity::intoThePotatoes();
226
        if (isset($this->attributes['support:info_file'])) {
227
            return MobileconfigSuperclass::BUFFER_CONSENT_PRE . htmlspecialchars(iconv("UTF-8", "UTF-8//TRANSLIT", $this->attributes['support:info_file'][0]), ENT_XML1, 'UTF-8') . MobileconfigSuperclass::BUFFER_CONSENT_POST;
228
        }
229
        if ($this->attributes['internal:verify_userinput_suffix'][0] != 0) {
230
            if ($this->attributes['internal:hint_userinput_suffix'][0] != 0) {
231
                $retval = MobileconfigSuperclass::BUFFER_CONSENT_PRE . sprintf(_("Important Notice: your username MUST end exactly with '...@%s' !"), $this->attributes['internal:realm'][0]) . MobileconfigSuperclass::BUFFER_CONSENT_POST;
232
                \core\common\Entity::outOfThePotatoes();
233
                return $retval;
234
            } else { 
235
            if (strlen($this->attributes['internal:realm'][0]) > 0) {
236
                /// note space between variable and exclamation mark - makes sure users don't mistakenly think the exclamation mark is part of the required username!
237
                $retval = MobileconfigSuperclass::BUFFER_CONSENT_PRE . sprintf(_("Important Notice: your username MUST contain an '@' and end with ...%s !"), $this->attributes['internal:realm'][0]) . MobileconfigSuperclass::BUFFER_CONSENT_POST;
238
                \core\common\Entity::outOfThePotatoes();
239
                return $retval;
240
            }
241
            $retval = MobileconfigSuperclass::BUFFER_CONSENT_PRE . _("Important Notice: your username MUST be in the form of xxx@yyy where the yyy is a common suffix identifying your Identity Provider. Please find out what to use there and enter the username in the correct format.") . MobileconfigSuperclass::BUFFER_CONSENT_POST;
242
            \core\common\Entity::outOfThePotatoes();
243
            return $retval;
244
            }
245
        }
246
        \core\common\Entity::outOfThePotatoes();
247
        return "";
248
    }
249
250
    /**
251
     * create the actual installer XML file
252
     * 
253
     * @return string filename of the generated installer
254
     */
255
    public function writeInstaller()
256
    {
257
        \core\common\Entity::intoThePotatoes();
258
259
        $this->loggerInstance->debug(4, "mobileconfig Module Installer start\n");
260
261
        // remove spaces and slashes (filename!), make sure it's simple ASCII only, then lowercase it
262
        // also escape htmlspecialchars
263
        // not all names and profiles have a name, so be prepared
264
265
        $this->loggerInstance->debug(5, "List of available attributes: " . var_export($this->attributes, TRUE));
266
267
        $this->instName = $this->attributes['general:instname'][0] ?? _("Unnamed Organisation");
268
        $this->profileName = $this->attributes['profile:name'][0] ?? _("Unnamed Profile");
269
270
        $this->massagedInst = $this->massageName($this->instName);
271
        $this->massagedProfile = $this->massageName($this->profileName);
272
        $this->massagedCountry = $this->massageName($this->attributes['internal:country'][0]);
273
        $this->massagedConsortium = $this->massageName(\config\ConfAssistant::CONSORTIUM['name']);
274
        $this->lang = preg_replace('/\..+/', '', setlocale(LC_ALL, "0"));
275
276
        $eapType = $this->selectedEap;
277
278
        $outputXml = self::FILE_START;
279
        $outputXml .= "<key>PayloadContent</key>
280
         <array>";
281
282
        // if we are in silverbullet, we will need a whole own block for the client credential
283
        // and also for the profile expiry
284
285
        $this->clientCertUUID = NULL;
286
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
287
            $blockinfo = $this->clientP12Block();
288
            $outputXml .= $blockinfo['block'];
289
            $this->clientCertUUID = $blockinfo['UUID'];
290
        }
291
292
        $outputXml .= $this->allCA();
293
294
        $outputXml .= $this->allNetworkBlocks();
295
296
        $outputXml .= "</array>";
297
        $outputXml .= $this->generalPayload();
298
        $outputXml .= $this->consentBlock();
299
300
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
301
            $outputXml .= $this->expiryBlock();
302
        }
303
        $outputXml .= self::FILE_END;
304
305
        file_put_contents('installer_profile', $outputXml);
306
307
        $fileName = $this->installerBasename . '.mobileconfig';
308
309
        if (!$this->sign) {
310
            rename("installer_profile", $fileName);
311
            \core\common\Entity::outOfThePotatoes();
312
            return $fileName;
313
        }
314
        // still here? Then we are signing.
315
        $retval = 0;
316
        $signing = system($this->sign . " installer_profile '$fileName' > /dev/null", $retval);
317
        if ($retval !== 0 || $signing === FALSE) {
318
            $this->loggerInstance->debug(2, "Signing the mobileconfig installer $fileName FAILED!\n");
319
            // we are passing a name that will be then used as a path - this will not exist, hence an error will
320
            // be generated
321
            return("no_go");
322
        }
323
        \core\common\Entity::outOfThePotatoes();
324
        return $fileName;
325
    }
326
327
    /**
328
     * produces the HTML text to be displayed when clicking on the "help" button
329
     * besides the download button.
330
     * 
331
     * @return string
332
     */
333
    public function writeDeviceInfo()
334
    {
335
        \core\common\Entity::intoThePotatoes();
336
        $ssidCount = 0;
337
        $oiCount = 0;
338
        foreach ($this->attributes['internal:networks'] as $netDetail) {
339
            $ssidCount = $ssidCount + count($netDetail['ssid']);
340
            $oiCount = $oiCount + count($netDetail['oi']);
341
        }
342
        $certCount = count($this->attributes['internal:CAs'][0]);
343
        $out = "<p>" . _("For best results, please use the built-in browser (Safari) to open the configuration file.") . "</p>";
344
        $out .= "<p>";
345
        $out .= _("The profile will install itself after you click (or tap) the button. You will be asked for confirmation/input at several points:");
346
        $out .= "<ul>";
347
        $out .= "<li>" . _("to install the profile") . "</li>";
348
        $out .= "<li>" . ngettext("to accept the server certificate authority", "to accept the server certificate authorities", $certCount);
349
        if ($certCount > 1) {
350
            $out .= " " . sprintf(_("(%d times)"), $certCount);
351
        }
352
        $out .= "</li>";
353
        $out .= "<li>" . _("to enter the username and password you have been given by your organisation");
354
        if ($ssidCount > 1) {
355
            $out .= " " . sprintf(_("(%d times each, because %d SSIDs and %d Passpoint networks are installed)"), $ssidCount+$oiCount, $ssidCount, $oiCount);
356
        }
357
        $out .= "</li>";
358
        $out .= "</ul>";
359
        $out .= "</p>";
360
        \core\common\Entity::outOfThePotatoes();
361
        return $out;
362
    }
363
364
    /**
365
     * collates a list of the UUIDs of all the CAs which are to be included in
366
     * the mobileconfig file
367
     * 
368
     * @return array
369
     */
370
    private function listCAUuids()
371
    {
372
        $retval = [];
373
        foreach ($this->attributes['internal:CAs'][0] as $ca) {
374
            $retval[] = $ca['uuid'];
375
        }
376
        return $retval;
377
    }
378
379
    /**
380
     * This is the XML structure subtree of a Network block which contains the
381
     * settings specific to Passpoint
382
     * 
383
     * @param array  $consortiumOi list of consortiumOi to put into structure
384
     * @param string $oiName       the pretty-print name of the RCOI
385
     * @return string
386
     */
387
    private function passPointBlock($consortiumOi, $oiName)
388
    {
389
        $retval = "
390
               <key>IsHotspot</key>
391
               <true/>
392
               <key>ServiceProviderRoamingEnabled</key>
393
               <true/>
394
               <key>DisplayedOperatorName</key>
395
               <string>" . $oiName . "</string>";
396
        // if we don't know the realm, omit the entire DomainName key
397
        if (isset($this->attributes['internal:realm'])) {
398
            $retval .= "<key>DomainName</key>
399
               <string>";
400
            $retval .= $this->attributes['internal:realm'][0];
401
            $retval .= "</string>
402
                ";
403
        }
404
        $retval .= "                <key>RoamingConsortiumOIs</key>
405
                <array>";
406
407
        foreach ($consortiumOi as $oneCons) {
408
            $retval .= "<string>" . strtoupper($oneCons) . "</string>";
409
        }
410
411
        $retval .= "</array>";
412
        // this is an undocumented value found on the net. Does it do something useful?
413
        $retval .= "<key>_UsingHotspot20</key>
414
                <true/>
415
                ";
416
        // do we need to set NAIRealmName ? In Rel 1, probably yes, in Rel 2, 
417
        // no because ConsortiumOI is enough.
418
        // but which release is OS X doing? And what should we fill in, given
419
        // that we have thousands of realms? Try just eduroam.org
420
        // 
421
        // tests from Hideaki suggest it's better not to set it; if Roaming
422
        // consortium OI and NAIRealmNames are both set, connecting to a hotspot
423
        // with just RCOI does not work
424
        /* if (\config\ConfAssistant::CONSORTIUM['name'] == "eduroam") {
425
            $retval .= "<key>NAIRealmNames</key>
426
                <array>
427
                    <string>eduroam.org</string>
428
                </array>";
429
        }*/
430
        return $retval;
431
    }
432
433
    /**
434
     * produces the EAP sub-block of a Network block
435
     * 
436
     * @param array $eapType EAP type in array notation
437
     * @return string
438
     */
439
    private function eapBlock($eapType)
440
    {
441
        $realm = $this->determineOuterIdString();
442
        $retval = "<key>EAPClientConfiguration</key>
443
                  <dict>
444
                      <key>AcceptEAPTypes</key>
445
                         <array>
446
                            <integer>" . $eapType['OUTER'] . "</integer>
447
                         </array>
448
                      <key>EAPFASTProvisionPAC</key>
449
                            <true />
450
                      <key>EAPFASTUsePAC</key>
451
                            <true />
452
                      <key>EAPFastProvisionPACAnonymously</key>
453
                            <false />
454
                      <key>OneTimeUserPassword</key>
455
                            <false />
456
";
457
        if ($realm !== NULL) {
458
            $retval .= "<key>OuterIdentity</key>
459
                                    <string>" . htmlspecialchars($realm, ENT_XML1, 'UTF-8') . "</string>
460
";
461
        }
462
        $retval .= "<key>PayloadCertificateAnchorUUID</key>
463
                         <array>";
464
        foreach ($this->listCAUuids() as $uuid) {
465
            if (in_array($uuid, $this->CAsAccountedFor)) {
466
                $retval .= "
467
<string>$uuid</string>";
468
            }
469
        }
470
        $retval .= "
471
                         </array>
472
                      <key>TLSAllowTrustExceptions</key>
473
                         <false />
474
                      <key>TLSTrustedServerNames</key>
475
                         <array>";
476
        foreach ($this->attributes['eap:server_name'] as $commonName) {
477
            $retval .= "
478
<string>$commonName</string>";
479
        }
480
        $retval .= "
481
                         </array>";
482
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
483
            $retval .= "<key>UserName</key><string>" . $this->clientCert["certObject"]->username . "</string>";
484
        }
485
        $retval .= "
486
                      <key>TTLSInnerAuthentication</key>
487
                         <string>" . ($eapType['INNER'] == \core\common\EAP::NE_PAP ? "PAP" : "MSCHAPv2") . "</string>
488
                   </dict>";
489
        return $retval;
490
    }
491
492
    /**
493
     * produces the Proxy sub-block of a Network block
494
     * 
495
     * @return string
496
     */
497
    protected function proxySettings()
498
    {
499
        $buffer = "<key>ProxyType</key>";
500
        if (isset($this->attributes['media:force_proxy'])) {
501
            // find the port delimiter. In case of IPv6, there are multiple ':' 
502
            // characters, so we have to find the LAST one
503
            $serverAndPort = explode(':', strrev($this->attributes['media:force_proxy'][0]), 2);
504
            // characters are still reversed, invert on use!
505
            $buffer .= "<string>Manual</string>
506
                  <key>ProxyServer</key>
507
                  <string>" . strrev($serverAndPort[1]) . "</string>
508
                  <key>ProxyServerPort</key>
509
                  <integer>" . strrev($serverAndPort[0]) . "</integer>
510
                  <key>ProxyPACFallbackAllowed</key>
511
                  <false/>";
512
        } else {
513
            $buffer .= "<string>Auto</string>
514
                  <key>ProxyPACFallbackAllowed</key>
515
                  <true/>";
516
        }
517
        return $buffer;
518
    }
519
520
    /**
521
     * iOS 8 introduced the new value "WPA2" to lock out WPA ("1"). Make sure
522
     * we use it for the most recent gen of mobileconfig for iOS.
523
     * 
524
     * According to reports on the eduroam dev ML, also recent macOS supports it
525
     * so add it there, too (spec is silent on macOS versions).
526
     * 
527
     * @return string either "WPA" or "WPA2
528
     */
529
    private function encryptionString() {
530
        if (
531
                get_class($this) == "devices\apple_mobileconfig\DeviceMobileconfigIos12plus" || 
532
                get_class($this) == "devices\apple_mobileconfig\DeviceMobileconfigOsX"
533
           ) {
534
                    return "WPA2";
535
                } else {
536
                    return "WPA";
537
                }
538
    }
539
    
540
    /**
541
     * produces an entire Network block
542
     * 
543
     * @param int                  $blocktype      which type of network block is this?
544
     * @param string|array|boolean $toBeConfigured variable part of the config. Single SSID or list of ConsortiumOi
545
     * @param string               $prettyName     name with which to present the network to users
546
     * @return string
547
     * @throws Exception
548
     */
549
    private function networkBlock($blocktype, $toBeConfigured, $prettyName)
550
    {
551
        \core\common\Entity::intoThePotatoes();
552
        $eapType = $this->selectedEap;
553
        switch ($blocktype) {
554
            case MobileconfigSuperclass::NETWORK_BLOCK_TYPE_SSID:
555
                if (!is_string($toBeConfigured)) {
556
                    throw new Exception("SSID must be a string!");
557
                }
558
                $escapedSSID = htmlspecialchars($toBeConfigured, ENT_XML1, 'UTF-8');
559
                $payloadIdentifier = "wifi." . $this->serial;
560
                $payloadShortName = sprintf(_("%s - SSID %s"), $prettyName, $escapedSSID);
561
                $payloadName = sprintf(_("%s configuration for network name %s"), $prettyName, $escapedSSID);
562
                $encryptionTypeString = $this->encryptionString();
563
                $setupModesString = "";
564
                $wifiNetworkIdentification = "<key>SSID_STR</key>
565
                  <string>$escapedSSID</string>";
566
                break;
567
            case MobileconfigSuperclass::NETWORK_BLOCK_TYPE_WIRED:
568
                if (!is_bool($toBeConfigured)) {
569
                    throw new Exception("We expected a TRUE here!");
570
                }
571
                $payloadIdentifier = "firstactiveethernet";
572
                $payloadShortName = sprintf(_("%s - Wired Network"), $prettyName);
573
                $payloadName = sprintf(_("%s configuration for wired network"), $prettyName);
574
                $encryptionTypeString = "any";
575
                $setupModesString = "
576
               <key>SetupModes</key>
577
                  <array>
578
                     <string>System</string>
579
                  </array>";
580
                $wifiNetworkIdentification = "";
581
                break;
582
            case MobileconfigSuperclass::NETWORK_BLOCK_TYPE_CONSORTIUMOIS:
583
                if (!is_array($toBeConfigured)) {
584
                    throw new Exception("ConsortiumOI must be an array!");
585
                }
586
                if (count($toBeConfigured) == 0) {
587
                    return "";
588
                }
589
                $payloadIdentifier = "hs20.".implode('-',$toBeConfigured);
590
                $payloadShortName = sprintf(_("%s - RCOI"), $prettyName);
591
                $payloadName = sprintf(_("%s configuration (Passpoint RCOI)"),$prettyName);
592
                $encryptionTypeString = $this->encryptionString();
593
                $setupModesString = "";
594
                $wifiNetworkIdentification = $this->passPointBlock($toBeConfigured, $prettyName);
595
                break;
596
            default:
597
                throw new Exception("This type of network block is unknown!");
598
        }
599
        $retval = "<dict>";
600
        $retval .= $this->eapBlock($eapType);
601
        $retval .= "<key>EncryptionType</key>
602
                  <string>$encryptionTypeString</string>
603
               <key>HIDDEN_NETWORK</key>
604
                  <false />
605
               <key>PayloadDescription</key>
606
                  <string>$payloadName</string>
607
               <key>PayloadDisplayName</key>
608
                  <string>$payloadShortName</string>
609
               <key>PayloadIdentifier</key>
610
                  <string>" . self::IPHONE_PAYLOAD_PREFIX . ".$this->massagedConsortium.$this->massagedCountry.$this->massagedInst.$this->massagedProfile.$this->lang.$payloadIdentifier</string>
611
               <key>PayloadOrganization</key>
612
                  <string>" . $this->massagedConsortium . ".1x-config.org</string>
613
               <key>PayloadType</key>
614
                  <string>com.apple." . ($blocktype == MobileconfigSuperclass::NETWORK_BLOCK_TYPE_WIRED ? "firstactiveethernet" : "wifi") . ".managed</string>";
615
        $retval .= $this->proxySettings();
616
        $retval .= $setupModesString;
617
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
618
            if ($this->clientCertUUID === NULL) {
619
                throw new Exception("Silverbullet REQUIRES a client certificate and we need to know the UUID!");
620
            }
621
            $retval .= "<key>PayloadCertificateUUID</key>
622
                        <string>$this->clientCertUUID</string>";
623
        }
624
        $retval .= "
625
               <key>PayloadUUID</key>
626
                  <string>" . \core\common\Entity::uuid() . "</string>
627
               <key>PayloadVersion</key>
628
                  <integer>1</integer>
629
                  $wifiNetworkIdentification</dict>";
630
        $this->serial = $this->serial + 1;
631
        \core\common\Entity::outOfThePotatoes();
632
        return $retval;
633
    }
634
635
    /**
636
     * Produces a Network block which sets a network to manual join (we don't
637
     * get any closer to removing a network in mobileconfig)
638
     * 
639
     * @param string $ssid the SSID to set to manual join only
640
     * @return string
641
     */
642
    private function removenetworkBlock($ssid)
643
    {
644
        \core\common\Entity::intoThePotatoes();
645
        $retval = "
646
<dict>
647
	<key>AutoJoin</key>
648
	<false/>
649
	<key>EncryptionType</key>
650
	<string>None</string>
651
	<key>HIDDEN_NETWORK</key>
652
	<false/>
653
	<key>IsHotspot</key>
654
	<false/>
655
	<key>PayloadDescription</key>
656
	<string>" . sprintf(_("This SSID should not be used after bootstrapping %s"), \config\ConfAssistant::CONSORTIUM['display_name']) . "</string>
657
	<key>PayloadDisplayName</key>
658
	<string>" . _("Disabled WiFi network") . "</string>
659
	<key>PayloadIdentifier</key>
660
	<string>" . self::IPHONE_PAYLOAD_PREFIX . ".$this->massagedConsortium.$this->massagedCountry.$this->massagedInst.$this->massagedProfile.$this->lang.wifi.disabled.$this->removeSerial</string>
661
	<key>PayloadType</key>
662
	<string>com.apple.wifi.managed</string>
663
	<key>PayloadUUID</key>
664
	<string>" . \core\common\Entity::uuid() . "</string>
665
	<key>PayloadVersion</key>
666
	<real>1</real>";
667
        $retval .= $this->proxySettings();
668
        $retval .= "<key>SSID_STR</key>
669
	<string>$ssid</string>
670
</dict>
671
";
672
        \core\common\Entity::outOfThePotatoes();
673
        return $retval;
674
    }
675
676
    const NETWORK_BLOCK_TYPE_SSID = 100;
677
    const NETWORK_BLOCK_TYPE_CONSORTIUMOIS = 101;
678
    const NETWORK_BLOCK_TYPE_WIRED = 102;
679
680
    /**
681
     * produces the entire series of Network blocks; all for SSID-based, 
682
     * Passpoint-based, wired, and manual-select only SSIDs
683
     * 
684
     * @return string
685
     */
686
    private function allNetworkBlocks()
687
    {
688
        $retval = "";
689
        $this->serial = 0;
690
        foreach ($this->attributes['internal:networks'] as $netName => $netDefinition) {
691
            // mobileconfig network blocks can only hold one SSID each, so iterate here
692
            foreach ($netDefinition['ssid'] as $ssid) {
693
                $retval .= $this->networkBlock(MobileconfigSuperclass::NETWORK_BLOCK_TYPE_SSID, $ssid, $netName);
694
            }
695
            // mobileconfig network blocks can accommodate a list of RCOIs in one statement, so just call it
696
            if ($this->selectedEapObject->isPasswordRequired() === FALSE || $netName != 'eduroam®') {
697
                $retval .= $this->networkBlock(MobileconfigSuperclass::NETWORK_BLOCK_TYPE_CONSORTIUMOIS, $netDefinition['oi'], $netName);
698
            }
699
        }
700
        if (isset($this->attributes['media:wired']) && get_class($this) == "devices\apple_mobileconfig\DeviceMobileconfigOsX") {
701
            $retval .= $this->networkBlock(MobileconfigSuperclass::NETWORK_BLOCK_TYPE_WIRED, TRUE, \config\ConfAssistant::CONSORTIUM['display_name']);
702
        }
703
        if (isset($this->attributes['media:remove_SSID'])) {
704
            $this->removeSerial = 0;
705
            foreach ($this->attributes['media:remove_SSID'] as $removeSSID) {
706
                $retval .= $this->removenetworkBlock($removeSSID);
707
                $this->removeSerial = $this->removeSerial + 1;
708
            }
709
        }
710
        return $retval;
711
    }
712
713
    /**
714
     * collates a block with all CAs that are to be included in the mobileconfig
715
     * 
716
     * @return string
717
     */
718
    private function allCA()
719
    {
720
        $retval = "";
721
        $this->caSerial = 0;
722
        foreach ($this->attributes['internal:CAs'][0] as $ca) {
723
            $retval .= $this->caBlob($ca);
724
            $this->caSerial = $this->caSerial + 1;
725
        }
726
        return $retval;
727
    }
728
729
    /**
730
     * creates a Cert block containing a client certificate (used in SB only)
731
     * @return array the block itself, and the UUID of the certificate
732
     * @throws Exception
733
     */
734
    private function clientP12Block()
735
    {
736
        \core\common\Entity::intoThePotatoes();
737
        if (count($this->clientCert) == 0) {
738
            throw new Exception("the client block was called but there is no client certificate!");
739
        }
740
        $binaryBlob = $this->clientCert["certdata_nointermediate"];
741
        $mimeBlob = base64_encode($binaryBlob);
742
        $mimeFormatted = chunk_split($mimeBlob, 52, "\r\n");
743
        $payloadUUID = \core\common\Entity::uuid('', $mimeBlob);
744
        $retArray = ["block" => "<dict>" .
745
            // we don't include the import password. It's displayed on screen, and should be input by the user.
746
            // <key>Password</key>
747
            //   <string>" . $this->clientCert['password'] . "</string>
748
            "<key>PayloadCertificateFileName</key>
749
                     <string>" . $this->massagedConsortium . ".pfx</string>
750
                  <key>PayloadContent</key>
751
                     <data>
752
$mimeFormatted
753
                     </data>
754
                  <key>PayloadDescription</key>
755
                     <string>MIME Base-64 encoded PKCS#12 Client Certificate</string>
756
                  <key>PayloadDisplayName</key>
757
                     <string>" . _("User certificate") . "</string>
758
                  <key>PayloadIdentifier</key>
759
                     <string>com.apple.security.pkcs12.$payloadUUID</string>
760
                  <key>PayloadType</key>
761
                     <string>com.apple.security.pkcs12</string>
762
                  <key>PayloadUUID</key>
763
                     <string>$payloadUUID</string>
764
                  <key>PayloadVersion</key>
765
                     <integer>1</integer>
766
                </dict>",
767
            "UUID" => $payloadUUID,];
768
        \core\common\Entity::outOfThePotatoes();
769
        return $retArray;
770
    }
771
772
    /**
773
     * creates an Expiry block. This is only done in SB; the profile expires
774
     * when the client cert expires.
775
     * 
776
     * @return string
777
     * @throws Exception
778
     */
779
    private function expiryBlock() {
780
        if (count($this->clientCert) == 0) {
781
            throw new Exception("the expiry block was called but there is no client certificate!");
782
        }
783
        $expiryTime = new \DateTime($this->clientCert['certObject']->expiry);
784
        return "<key>RemovalDate</key>
785
        <date>" . $expiryTime->format("Y-m-d") . "T" . $expiryTime->format("H:i:s") . "Z</date>";
786
    }
787
788
    /**
789
     * creates a block for one single CA
790
     * 
791
     * @param array $ca the CA for which to generate the XML block
792
     * @return string
793
     */
794
    private function caBlob($ca)
795
    {
796
        \core\common\Entity::intoThePotatoes();
797
        $stream = "";
798
        if (!in_array($ca['uuid'], $this->CAsAccountedFor)) { // skip if this is a duplicate
799
            // cut lines with CERTIFICATE
800
            $stage1 = preg_replace('/-----BEGIN CERTIFICATE-----/', '', $ca['pem']);
801
            $stage2 = preg_replace('/-----END CERTIFICATE-----/', '', $stage1);
802
            $trimmedPem = trim($stage2);
803
804
            $stream = "
805
            <dict>
806
               <key>PayloadCertificateFileName</key>
807
               <string>" . $ca['uuid'] . ".der</string>
808
               <key>PayloadContent</key>
809
               <data>
810
" . $trimmedPem . "</data>
811
               <key>PayloadDescription</key>
812
               <string>" . sprintf(_("The %s Certification Authority"), \core\common\Entity::$nomenclature_idp) . "</string>
813
               <key>PayloadDisplayName</key>
814
               <string>" . 
815
                    /// example: "Identity Provider CA #1 (Root)"
816
                    sprintf(_("%s CA #%d (%s)" ), 
817
                            \core\common\Entity::$nomenclature_idp, 
818
                            count($this->CAsAccountedFor)+1, 
819
                            ($ca['root'] ? _("Root") : _("Intermediate"))) . 
820
              "</string>
821
               <key>PayloadIdentifier</key>
822
               <string>" . self::IPHONE_PAYLOAD_PREFIX . ".$this->massagedConsortium.$this->massagedCountry.$this->massagedInst.$this->massagedProfile.credential.$this->caSerial</string>
823
               <key>PayloadOrganization</key>
824
               <string>" . $this->massagedConsortium . ".1x-config.org</string>
825
               <key>PayloadType</key>
826
               <string>com.apple.security.root</string>
827
               <key>PayloadUUID</key><string>" . $ca['uuid'] . "</string>
828
               <key>PayloadVersion</key>
829
               <integer>1</integer>
830
            </dict>";
831
            $this->CAsAccountedFor[] = $ca['uuid'];
832
        }
833
        \core\common\Entity::outOfThePotatoes();
834
        return $stream;
835
    }
836
837
}
838