Passed
Push — master ( 067459...50896f )
by Stefan
08:15
created

DeviceXML::marshalObject()   B

Complexity

Conditions 11
Paths 32

Size

Total Lines 37
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 27
dl 0
loc 37
rs 7.3166
c 0
b 0
f 0
cc 11
nc 32
nop 2

How to fix   Complexity   

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 defines an abstract class used for generic XML
25
 * devices
26
 * actual modules only define available EAP types.
27
 *
28
 * @author Maja Gorecka-Wolniewicz <[email protected]>
29
 * @author Tomasz Wolniewicz <[email protected]>
30
 *
31
 * @package ModuleWriting
32
 */
33
34
namespace devices\xml;
35
36
use Exception;
37
38
/**
39
 * This class implements full functionality of the generic XML device
40
 * the only fuction of the extenstions of this class is to specify
41
 * supported EAP methods.
42
 * Instead of specifying supported EAPS an extension can set $all_eaps to true
43
 * this will cause the installer to configure all EAP methods supported by 
44
 * the current profile and declared by the given device.
45
 */
46
abstract class DeviceXML extends \core\DeviceConfig {
47
48
    /**
49
     * construct the device
50
     */
51
    public function __construct() {
52
        parent::__construct();
53
    }
54
55
    /**
56
     * $langScope can be 'global' when all lang and all lang-specific information
57
     * is dumped or 'single' when only the selected lang (and defaults) are passed
58
     * NOTICE: 'global' is not yet supported
59
     * 
60
     * @var string
61
     */
62
    public $langScope;
63
64
    /**
65
     * whether all EAP types should be included in the file or only the 
66
     * preferred one
67
     * 
68
     * @var boolean
69
     */
70
    public $allEaps = FALSE;
71
72
    /**
73
     * vendor-specific additional information
74
     * 
75
     * @var array
76
     */
77
    public $VendorSpecific;
78
79
    /**
80
     * create HTML code explaining the installer
81
     * 
82
     * @return string
83
     */
84
    public function writeDeviceInfo() {
85
        \core\common\Entity::intoThePotatoes();
86
        $out = "<p>";
87
        $out .= sprintf(_("This is a generic configuration file in the IETF <a href='%s'>EAP Metadata -00</a> XML format."), "https://tools.ietf.org/html/draft-winter-opsawg-eap-metadata-00");
88
        \core\common\Entity::outOfThePotatoes();
89
        return $out;
90
    }
91
92
    /**
93
     * create the actual XML file
94
     * 
95
     * @return string filename of the generated installer
96
     * @throws Exception
97
     *
98
     */
99
    public function writeInstaller() {
100
        $attr = $this->attributes;
101
        $NAMESPACE = 'urn:RFC4282:realm';
102
//EAPIdentityProvider  begin
103
        $eapIdp = new EAPIdentityProvider();
104
        $eapIdp->setProperty('CredentialApplicability', $this->getCredentialApplicability());
105
//    $eap_idp->setProperty('ValidUntil',$this->getValidUntil());
106
// ProviderInfo->
107
        $eapIdp->setProperty('ProviderInfo', $this->getProviderInfo());
108
// TODO    $eap_idp->setProperty('VendorSpecific',$this->getVendorSpecific());
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
109
        $methodList = [];
110
        if ($this->allEaps) {
111
            $eapmethods = [];
112
            foreach ($attr['all_eaps'] as $eap) {
113
                $eapRep = $eap->getArrayRep();
114
                if (in_array($eapRep, $this->supportedEapMethods)) {
115
                    $eapmethods[] = $eapRep;
116
                }
117
            }
118
        } else {
119
            $eapmethods = [$this->selectedEap];
120
        }
121
        foreach ($eapmethods as $eap) {
122
            $methodList[] = $this->getAuthMethod($eap);
123
        }
124
        $authMethods = new AuthenticationMethods();
125
        $authMethods->setProperty('AuthenticationMethods', $methodList);
126
        $eapIdp->setProperty('AuthenticationMethods', $authMethods);
127
        if (empty($attr['internal:realm'][0])) {
128
            $eapIdp->setAttribute('ID', 'undefined');
129
            $eapIdp->setAttribute('namespace', 'urn:undefined');
130
        } else {
131
            $eapIdp->setAttribute('ID', $attr['internal:realm'][0]);
132
            $eapIdp->setAttribute('namespace', $NAMESPACE);
133
        }
134
        if ($this->langScope === 'single') {
135
            $eapIdp->setAttribute('lang', $this->languageInstance->getLang());
136
        }
137
        $eapIdp->setAttribute('version', '1');
138
139
140
// EAPIdentityProvider end
141
// Generate XML
142
143
        $rootname = 'EAPIdentityProviderList';
144
        $root = new \SimpleXMLElement("<?xml version=\"1.0\" encoding=\"utf-8\" ?><{$rootname} xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"eap-metadata.xsd\"></{$rootname}>");
145
146
        $this->marshalObject($root, $eapIdp);
147
        $dom = dom_import_simplexml($root)->ownerDocument;
148
        //TODO schema validation makes sense so probably should be used
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
149
        if ($dom->schemaValidate(ROOT . '/devices/xml/eap-metadata.xsd') === FALSE) {
150
            throw new Exception("Schema validation failed for eap-metadata");
151
        }
152
        file_put_contents($this->installerBasename . '.eap-config', $dom->saveXML());
153
        return($this->installerBasename . '.eap-config');
154
    }
155
156
    private const ATTRIBUTENAMES = [
157
        'support:email' => 'EmailAddress',
158
        'support:url' => 'WebAddress',
159
        'support:phone' => 'Phone',
160
        'profile:description' => 'Description',
161
        'support:info_file' => 'TermsOfUse',
162
        'general:logo_file' => 'ProviderLogo',
163
    ];
164
165
    /**
166
     * 
167
     * @param string $attrName the attribute name
168
     * @return array of values for this attribute
169
     */
170
    private function getSimpleMLAttribute($attrName) {
171
        if (empty($this->attributes[$attrName][0])) {
172
            return([]);
173
        }
174
        $attributeList = $this->attributes[$attrName];
175
        if (!isset(self::ATTRIBUTENAMES[$attrName])) {
176
            $this->loggerInstance->debug(4, "Missing class definition for $attrName\n");
177
            return([]);
178
        }
179
        $className = "\devices\xml\\" . self::ATTRIBUTENAMES[$attrName];
180
        $objs = [];
181
        if ($this->langScope === 'global') {
182
            foreach ($attributeList['langs'] as $language => $value) {
183
                $language = ($language === 'C' ? 'any' : $language);
184
                $obj = new $className();
185
                $obj->setValue($value);
186
                $obj->setAttributes(['lang' => $language]);
187
                $objs[] = $obj;
188
            }
189
        } else {
190
            $obj = new $className();
191
            $obj->setValue($attributeList[0]);
192
            $objs[] = $obj;
193
        }
194
        return($objs);
195
    }
196
197
    /**
198
     * constructs the name of the institution and puts it into the XML.
199
     * consists of the best-language-match inst name, and if the inst has more 
200
     * than one profile also the best-language-match profile name
201
     * 
202
     * @return \devices\xml\DisplayName[]
203
     */
204
    private function getDisplayName() {
205
        $attr = $this->attributes;
206
        $objs = [];
207
        if ($this->langScope === 'global') {
208
            $instNameLangs = $attr['general:instname']['langs'];
209
            if ($attr['internal:profile_count'][0] > 1) {
210
                $profileNameLangs = $attr['profile:name']['langs'];
211
            }
212
            foreach ($instNameLangs as $language => $value) {
213
                $language = ($language === 'C' ? 'any' : $language);
214
                $displayname = new DisplayName();
215
                if (isset($profileNameLangs)) {
216
                    $langOrC = isset($profileNameLangs[$language]) ? $profileNameLangs[$language] : $profileNameLangs['C'];
217
                    $value .= ' - ' . $langOrC;
218
                }
219
                $displayname->setValue($value);
220
                $displayname->setAttributes(['lang' => $language]);
221
                $objs[] = $displayname;
222
            }
223
        } else {
224
            $displayname = new DisplayName();
225
            $value = $attr['general:instname'][0];
226
            if ($attr['internal:profile_count'][0] > 1) {
227
                $value .= ' - ' . $attr['profile:name'][0];
228
            }
229
            $displayname->setValue($value);
230
            $objs[] = $displayname;
231
        }
232
        return $objs;
233
    }
234
235
    /**
236
     * retrieves the provider logo and puts it into the XML structure
237
     * 
238
     * @return \devices\xml\ProviderLogo
239
     */
240
    private function getProviderLogo() {
241
        $attr = $this->attributes;
242
        if (isset($attr['general:logo_file'][0])) {
243
            $logoString = base64_encode($attr['general:logo_file'][0]);
244
            $logoMime = 'image/' . $attr['internal:logo_file'][0]['mime'];
245
            $providerlogo = new ProviderLogo();
246
            $providerlogo->setAttributes(['mime' => $logoMime, 'encoding' => 'base64']);
247
            $providerlogo->setValue($logoString);
248
            return $providerlogo;
249
        }
250
    }
251
252
    /**
253
     * retrieves provider information and puts it into the XML structure.
254
     * contains the profile description and the ToU file, if any
255
     * 
256
     * @return \devices\xml\ProviderInfo
257
     */
258
    private function getProviderInfo() {
259
        $providerinfo = new ProviderInfo();
260
        $providerinfo->setProperty('DisplayName', $this->getDisplayName());
261
        $providerinfo->setProperty('Description', $this->getSimpleMLAttribute('profile:description'));
262
        $providerinfo->setProperty('ProviderLocation', $this->getProviderLocation());
263
        $providerinfo->setProperty('ProviderLogo', $this->getProviderLogo());
264
        $providerinfo->setProperty('TermsOfUse', $this->getSimpleMLAttribute('support:info_file'));
265
        $providerinfo->setProperty('Helpdesk', $this->getHelpdesk());
266
        return $providerinfo;
267
    }
268
269
    /**
270
     * retrieves the location information and puts it into the XML structure
271
     * 
272
     * @return \devices\xml\ProviderLocation|\devices\xml\ProviderLocation[]
273
     */
274
    private function getProviderLocation() {
275
        $attr = $this->attributes;
276
        if (isset($attr['general:geo_coordinates'])) {
277
            $attrCoordinates = $attr['general:geo_coordinates'];
278
            if (count($attrCoordinates) > 1) {
279
                $location = [];
280
                foreach ($attrCoordinates as $a) {
281
                    $providerlocation = new ProviderLocation();
282
                    $b = json_decode($a, true);
283
                    $providerlocation->setProperty('Longitude', $b['lon']);
284
                    $providerlocation->setProperty('Latitude', $b['lat']);
285
                    $location[] = $providerlocation;
286
                }
287
            } else {
288
                $providerlocation = new ProviderLocation();
289
                $b = json_decode($attrCoordinates[0], true);
290
                $providerlocation->setProperty('Longitude', $b['lon']);
291
                $providerlocation->setProperty('Latitude', $b['lat']);
292
                $location = $providerlocation;
293
            }
294
            return $location;
295
        }
296
    }
297
298
    /**
299
     * retrieves helpdesk contact information and puts it into the XML structure
300
     * 
301
     * @return \devices\xml\Helpdesk
302
     */
303
    private function getHelpdesk() {
304
        $helpdesk = new Helpdesk();
305
        $helpdesk->setProperty('EmailAddress', $this->getSimpleMLAttribute('support:email'));
306
        $helpdesk->setProperty('WebAddress', $this->getSimpleMLAttribute('support:url'));
307
        $helpdesk->setProperty('Phone', $this->getSimpleMLAttribute('support:phone'));
308
        return $helpdesk;
309
    }
310
311
    /**
312
     * determine where this credential should be applicable
313
     * 
314
     * @return \devices\xml\CredentialApplicability
315
     */
316
    private function getCredentialApplicability() {
317
        $ssids = $this->attributes['internal:SSID'];
318
        $oids = $this->attributes['internal:consortia'];
319
        $credentialapplicability = new CredentialApplicability();
320
        $ieee80211s = [];
321
        foreach ($ssids as $ssid => $ciph) {
322
            $ieee80211 = new IEEE80211();
323
            $ieee80211->setProperty('SSID', $ssid);
324
            $ieee80211->setProperty('MinRSNProto', $ciph == 'AES' ? 'CCMP' : 'TKIP');
325
            $ieee80211s[] = $ieee80211;
326
        }
327
        foreach ($oids as $oid) {
328
            $ieee80211 = new IEEE80211();
329
            $ieee80211->setProperty('ConsortiumOID', $oid);
330
            $ieee80211s[] = $ieee80211;
331
        }
332
        $credentialapplicability->setProperty('IEEE80211', $ieee80211s);
333
        return $credentialapplicability;
334
    }
335
336
    /**
337
     * retrieves the parameters needed for the given EAP method and creates
338
     * appropriate nodes in the XML structure for them
339
     * 
340
     * @param array $eap the EAP type in question
341
     * @return array a recap of the findings
342
     */
343
    private function getAuthenticationMethodParams($eap) {
344
        $inner = \core\common\EAP::innerAuth($eap);
345
        $outerMethod = $eap["OUTER"];
346
347
        if (isset($inner["METHOD"]) && $inner["METHOD"]) {
348
            $innerauthmethod = new InnerAuthenticationMethod();
349
            $typeOfInner = "\devices\xml\\" . ($inner["EAP"] ? 'EAPMethod' : 'NonEAPAuthMethod');
350
            $eapmethod = new $typeOfInner();
351
            $eaptype = new Type();
352
            $eaptype->setValue($inner['METHOD']);
353
            $eapmethod->setProperty('Type', $eaptype);
354
            $innerauthmethod->setProperty($typeOfInner, $eapmethod);
355
            return ['inner_method' => $innerauthmethod, 'methodID' => $outerMethod, 'inner_methodID' => $inner['METHOD']];
356
        } else {
357
            return ['inner_method' => 0, 'methodID' => $outerMethod, 'inner_methodID' => 0];
358
        }
359
    }
360
361
    /**
362
     * sets the server-side credentials for a given EAP type
363
     * 
364
     * @param \devices\XML\Type $eaptype the EAP type
365
     * @return \devices\XML\ServerSideCredential
366
     */
367
    private function setServerSideCredentials($eaptype) {
368
        $attr = $this->attributes;
369
        $serversidecredential = new ServerSideCredential();
370
// Certificates and server names
371
        $cAlist = [];
372
        $attrCaList = $attr['internal:CAs'][0];
373
        foreach ($attrCaList as $ca) {
374
            $caObject = new CA();
375
            $caObject->setValue(base64_encode($ca['der']));
376
            $caObject->setAttributes(['format' => 'X.509', 'encoding' => 'base64']);
377
            $cAlist[] = $caObject;
378
        }
379
        $serverids = [];
380
        $servers = $attr['eap:server_name'];
381
        foreach ($servers as $server) {
382
            $serverid = new ServerID();
383
            $serverid->setValue($server);
384
            $serverids[] = $serverid;
385
        }
386
        $serversidecredential->setProperty('EAPType', $eaptype->getValue());
387
        $serversidecredential->setProperty('CA', $cAlist);
388
        $serversidecredential->setProperty('ServerID', $serverids);
389
        return $serversidecredential;
390
    }
391
392
    /**
393
     * sets the realm information for the client-side credential
394
     * 
395
     * @param \devices\XML\ClientSideCredential $clientsidecredential the ClientSideCredential to which the realm info is to be added
396
     * @return void
397
     */
398
    private function setClientSideRealm($clientsidecredential) {
399
        $attr = $this->attributes;
400
        $realm = \core\common\Entity::getAttributeValue($attr, 'internal:realm', 0);
401
        if ($realm === NULL) {
402
            return;
403
        }
404
        if (\core\common\Entity::getAttributeValue($attr, 'internal:verify_userinput_suffix', 0) !== 1) {
405
            return;
406
        }
407
        $clientsidecredential->setProperty('InnerIdentitySuffix', $realm);
408
        if (\core\common\Entity::getAttributeValue($attr, 'internal:hint_userinput_suffix', 0) === 1) {
409
            $clientsidecredential->setProperty('InnerIdentityHint', 'true');
410
        }
411
    }
412
413
    /**
414
     * sets the client certificate
415
     * 
416
     * @return \devices\XML\ClientCertificate
417
     */
418
    private function setClientCertificate() {
419
        $clientCertificateObject = new ClientCertificate();
420
        $clientCertificateObject->setValue(base64_encode($this->clientCert["certdata"]));
421
        $clientCertificateObject->setAttributes(['format' => 'PKCS12', 'encoding' => 'base64']);
422
        return $clientCertificateObject;
423
    }
424
425
    /**
426
     * sets the client-side credentials for the given EAP type
427
     * 
428
     * @param array $eapParams the EAP parameters
429
     * @return \devices\XML\ClientSideCredential
430
     */
431
    private function setClientSideCredentials($eapParams) {
432
        $clientsidecredential = new ClientSideCredential();
433
        $outerId = $this->determineOuterIdString();
434
        if ($outerId !== NULL) {
435
            $clientsidecredential->setProperty('OuterIdentity', $outerId);
436
        }
437
        $this->setClientSideRealm($clientsidecredential);
438
        $clientsidecredential->setProperty('EAPType', $eapParams['inner_methodID'] ? $eapParams['inner_methodID'] : $eapParams['methodID']);
439
440
        // Client Certificate
441
        if ($this->selectedEap == \core\common\EAP::EAPTYPE_SILVERBULLET) {
442
            $clientsidecredential->setProperty('ClientCertificate', $this->setClientCertificate());
443
        }
444
        return $clientsidecredential;
445
    }
446
447
    /**
448
     * sets the EAP method
449
     * 
450
     * @param \devices\XML\Type $eaptype the EAP type XMLObject
451
     * @return \devices\XML\EAPMethod
452
     */
453
    private function setEapMethod($eaptype) {
454
        $eapmethod = new EAPMethod();
455
        $eapmethod->setProperty('Type', $eaptype);
456
        if (isset($this->VendorSpecific)) {
457
            $vendorspecifics = [];
458
            foreach ($this->VendorSpecific as $vs) {
459
                $vendorspecific = new VendorSpecific();
460
                $vs['value']->addAttribute('xsi:noNamespaceSchemaLocation', "xxx.xsd");
461
                $vendorspecific->setValue($vs['value']);
462
                $vendorspecific->setAttributes(['vendor' => $vs['vendor']]);
463
                $vendorspecifics[] = $vendorspecific;
464
            }
465
            $eapmethod->setProperty('VendorSpecific', $vendorspecifics);
466
        }
467
        return($eapmethod);
468
    }
469
470
    /**
471
     * determines the authentication method to use
472
     * 
473
     * @param array $eap the EAP methods, in array representation
474
     * @return \devices\xml\AuthenticationMethod
475
     */
476
    private function getAuthMethod($eap) {
477
        //       $attr = $this->attributes;
478
        $authmethod = new AuthenticationMethod();
479
        $eapParams = $this->getAuthenticationMethodParams($eap);
480
        $eaptype = new Type();
481
        $eaptype->setValue($eapParams['methodID']);
482
// Type
483
        $authmethod->setProperty('EAPMethod', $this->setEapMethod($eaptype));
484
485
// ServerSideCredentials
486
        $authmethod->setProperty('ServerSideCredential', $this->setServerSideCredentials($eaptype));
487
488
// ClientSideCredentials
489
        $authmethod->setProperty('ClientSideCredential', $this->setClientSideCredentials($eapParams));
490
491
        if ($eapParams['inner_method']) {
492
            $authmethod->setProperty('InnerAuthenticationMethod', $eapParams['inner_method']);
493
        }
494
        return $authmethod;
495
    }
496
497
    /**
498
     * 
499
     * @param \SimpleXMLElement   $node   the XML node to marshal
500
     * @param EAPIdentityProvider $object the Object
501
     * @return void
502
     */
503
    private function marshalObject($node, $object) {
504
        $qualClassName = get_class($object);
505
        // remove namespace qualifier
506
        $pos = strrpos($qualClassName, '\\');
507
        $className = substr($qualClassName, $pos + 1);
508
        $name = preg_replace("/_/", "-", $className);
509
        if ($object->getValue()) {
510
            $val = preg_replace('/&/', '&amp;', $object->getValue());
511
            $childNode = $node->addChild($name, $val);
512
        } else {
513
            $childNode = $node->addChild($name);
514
        }
515
        if ($object->areAttributes()) {
516
            $attrs = $object->getAttributes();
517
            foreach ($attrs as $attrt => $attrv) {
518
                $childNode->addAttribute($attrt, $attrv);
519
            }
520
        }
521
        $fields = $object->getAll();
522
        if (empty($fields)) {
523
            return;
524
        }
525
        foreach ($fields as $name => $value) {
526
            if (is_scalar($value)) {
527
                $childNode->addChild($name, strval($value));
528
                continue;
529
            }
530
            if (gettype($value) == 'array') {
531
                foreach ($value as $insideValue) {
532
                    if (is_object($insideValue)) {
533
                        $this->marshalObject($childNode, $insideValue);
534
                    }
535
                }
536
                continue;
537
            }
538
            if (gettype($value) == 'object') {
539
                $this->marshalObject($childNode, $value);
540
            }
541
        }
542
    }
543
544
}
545