Passed
Push — master ( abfa64...7415c5 )
by Tomasz
06:46
created

DeviceXML   F

Complexity

Total Complexity 70

Size/Duplication

Total Lines 512
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 70
eloc 260
c 4
b 0
f 0
dl 0
loc 512
rs 2.8

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 2 1
A writeDeviceInfo() 0 6 1
B writeInstaller() 0 56 8
A getProviderLogo() 0 9 2
A getProviderLocation() 0 21 4
A getHelpdesk() 0 6 1
A setServerSideCredentials() 0 23 3
A setClientSideRealm() 0 12 4
A setClientCertificate() 0 5 1
A getProviderInfo() 0 9 1
A setClientSideCredentials() 0 14 4
A getAuthMethod() 0 19 2
B getDisplayName() 0 29 8
B getSimpleMLAttribute() 0 25 6
A setEapMethod() 0 15 3
A getCredentialApplicability() 0 18 4
A innerAuth() 0 10 2
B marshalObject() 0 37 11
A getAuthenticationMethodParams() 0 15 4

How to fix   Complexity   

Complex Class

Complex classes like DeviceXML 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 DeviceXML, and based on these observations, apply Extract Interface, too.

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());
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
149
        if ($dom->schemaValidate(ROOT . '/devices/xml/eap-metadata.xsd') === FALSE) {
150
            throw new Exception("Schema validation failed for eap-metadata");
151
        }
152
        $dom->formatOutput = true;
153
        file_put_contents($this->installerBasename . '.eap-config', $dom->saveXML());
154
        return($this->installerBasename . '.eap-config');
155
    }
156
157
    private const ATTRIBUTENAMES = [
158
        'support:email' => 'EmailAddress',
159
        'support:url' => 'WebAddress',
160
        'support:phone' => 'Phone',
161
        'profile:description' => 'Description',
162
        'support:info_file' => 'TermsOfUse',
163
        'general:logo_file' => 'ProviderLogo',
164
    ];
165
    
166
    /**
167
     * determines the inner authentication. Is it EAP, and which mechanism is used to convey actual auth data
168
     * @param array $eap the EAP type for which we want to get the inner auth
169
     * @return array
170
     */
171
    private function innerAuth($eap)
172
    {
173
        $out = [];
174
        $out['METHOD'] = $eap["INNER"];
175
        $out['EAP'] = 0;
176
        // override if there is an inner EAP
177
        if ($eap["INNER"] > 0) { // there is an inner EAP method
178
            $out['EAP'] = 1;
179
        }
180
        return $out;
181
    }
182
183
    /**
184
     * 
185
     * @param string $attrName the attribute name
186
     * @return array of values for this attribute
187
     */
188
    private function getSimpleMLAttribute($attrName) {
189
        if (empty($this->attributes[$attrName][0])) {
190
            return([]);
191
        }
192
        $attributeList = $this->attributes[$attrName];
193
        if (!isset(self::ATTRIBUTENAMES[$attrName])) {
194
            $this->loggerInstance->debug(4, "Missing class definition for $attrName\n");
195
            return([]);
196
        }
197
        $className = "\devices\xml\\" . self::ATTRIBUTENAMES[$attrName];
198
        $objs = [];
199
        if ($this->langScope === 'global') {
200
            foreach ($attributeList['langs'] as $language => $value) {
201
                $language = ($language === 'C' ? 'any' : $language);
202
                $obj = new $className();
203
                $obj->setValue($value);
204
                $obj->setAttributes(['lang' => $language]);
205
                $objs[] = $obj;
206
            }
207
        } else {
208
            $obj = new $className();
209
            $obj->setValue($attributeList[0]);
210
            $objs[] = $obj;
211
        }
212
        return($objs);
213
    }
214
215
    /**
216
     * constructs the name of the institution and puts it into the XML.
217
     * consists of the best-language-match inst name, and if the inst has more 
218
     * than one profile also the best-language-match profile name
219
     * 
220
     * @return \devices\xml\DisplayName[]
221
     */
222
    private function getDisplayName() {
223
        $attr = $this->attributes;
224
        $objs = [];
225
        if ($this->langScope === 'global') {
226
            $instNameLangs = $attr['general:instname']['langs'];
227
            if ($attr['internal:profile_count'][0] > 1) {
228
                $profileNameLangs = $attr['profile:name']['langs'];
229
            }
230
            foreach ($instNameLangs as $language => $value) {
231
                $language = ($language === 'C' ? 'any' : $language);
232
                $displayname = new DisplayName();
233
                if (isset($profileNameLangs)) {
234
                    $langOrC = isset($profileNameLangs[$language]) ? $profileNameLangs[$language] : $profileNameLangs['C'];
235
                    $value .= ' - ' . $langOrC;
236
                }
237
                $displayname->setValue($value);
238
                $displayname->setAttributes(['lang' => $language]);
239
                $objs[] = $displayname;
240
            }
241
        } else {
242
            $displayname = new DisplayName();
243
            $value = $attr['general:instname'][0];
244
            if ($attr['internal:profile_count'][0] > 1) {
245
                $value .= ' - ' . $attr['profile:name'][0];
246
            }
247
            $displayname->setValue($value);
248
            $objs[] = $displayname;
249
        }
250
        return $objs;
251
    }
252
253
    /**
254
     * retrieves the provider logo and puts it into the XML structure
255
     * 
256
     * @return \devices\xml\ProviderLogo
257
     */
258
    private function getProviderLogo() {
259
        $attr = $this->attributes;
260
        if (isset($attr['general:logo_file'][0])) {
261
            $logoString = base64_encode($attr['general:logo_file'][0]);
262
            $logoMime = 'image/' . $attr['internal:logo_file'][0]['mime'];
263
            $providerlogo = new ProviderLogo();
264
            $providerlogo->setAttributes(['mime' => $logoMime, 'encoding' => 'base64']);
265
            $providerlogo->setValue($logoString);
266
            return $providerlogo;
267
        }
268
    }
269
270
    /**
271
     * retrieves provider information and puts it into the XML structure.
272
     * contains the profile description and the ToU file, if any
273
     * 
274
     * @return \devices\xml\ProviderInfo
275
     */
276
    private function getProviderInfo() {
277
        $providerinfo = new ProviderInfo();
278
        $providerinfo->setProperty('DisplayName', $this->getDisplayName());
279
        $providerinfo->setProperty('Description', $this->getSimpleMLAttribute('profile:description'));
280
        $providerinfo->setProperty('ProviderLocation', $this->getProviderLocation());
281
        $providerinfo->setProperty('ProviderLogo', $this->getProviderLogo());
282
        $providerinfo->setProperty('TermsOfUse', $this->getSimpleMLAttribute('support:info_file'));
283
        $providerinfo->setProperty('Helpdesk', $this->getHelpdesk());
284
        return $providerinfo;
285
    }
286
287
    /**
288
     * retrieves the location information and puts it into the XML structure
289
     * 
290
     * @return \devices\xml\ProviderLocation|\devices\xml\ProviderLocation[]
291
     */
292
    private function getProviderLocation() {
293
        $attr = $this->attributes;
294
        if (isset($attr['general:geo_coordinates'])) {
295
            $attrCoordinates = $attr['general:geo_coordinates'];
296
            if (count($attrCoordinates) > 1) {
297
                $location = [];
298
                foreach ($attrCoordinates as $a) {
299
                    $providerlocation = new ProviderLocation();
300
                    $b = json_decode($a, true);
301
                    $providerlocation->setProperty('Longitude', $b['lon']);
302
                    $providerlocation->setProperty('Latitude', $b['lat']);
303
                    $location[] = $providerlocation;
304
                }
305
            } else {
306
                $providerlocation = new ProviderLocation();
307
                $b = json_decode($attrCoordinates[0], true);
308
                $providerlocation->setProperty('Longitude', $b['lon']);
309
                $providerlocation->setProperty('Latitude', $b['lat']);
310
                $location = $providerlocation;
311
            }
312
            return $location;
313
        }
314
    }
315
316
    /**
317
     * retrieves helpdesk contact information and puts it into the XML structure
318
     * 
319
     * @return \devices\xml\Helpdesk
320
     */
321
    private function getHelpdesk() {
322
        $helpdesk = new Helpdesk();
323
        $helpdesk->setProperty('EmailAddress', $this->getSimpleMLAttribute('support:email'));
324
        $helpdesk->setProperty('WebAddress', $this->getSimpleMLAttribute('support:url'));
325
        $helpdesk->setProperty('Phone', $this->getSimpleMLAttribute('support:phone'));
326
        return $helpdesk;
327
    }
328
329
    /**
330
     * determine where this credential should be applicable
331
     * 
332
     * @return \devices\xml\CredentialApplicability
333
     */
334
    private function getCredentialApplicability() {
335
        $ssids = $this->attributes['internal:SSID'];
336
        $oids = $this->attributes['internal:consortia'];
337
        $credentialapplicability = new CredentialApplicability();
338
        $ieee80211s = [];
339
        foreach ($ssids as $ssid => $ciph) {
340
            $ieee80211 = new IEEE80211();
341
            $ieee80211->setProperty('SSID', $ssid);
342
            $ieee80211->setProperty('MinRSNProto', $ciph == 'AES' ? 'CCMP' : 'TKIP');
343
            $ieee80211s[] = $ieee80211;
344
        }
345
        foreach ($oids as $oid) {
346
            $ieee80211 = new IEEE80211();
347
            $ieee80211->setProperty('ConsortiumOID', $oid);
348
            $ieee80211s[] = $ieee80211;
349
        }
350
        $credentialapplicability->setProperty('IEEE80211', $ieee80211s);
351
        return $credentialapplicability;
352
    }
353
354
    /**
355
     * retrieves the parameters needed for the given EAP method and creates
356
     * appropriate nodes in the XML structure for them
357
     * 
358
     * @param array $eap the EAP type in question
359
     * @return array a recap of the findings
360
     */
361
    private function getAuthenticationMethodParams($eap) {
362
        $inner = $this->innerAuth($eap);
363
        $outerMethod = $eap["OUTER"];
364
365
        if (isset($inner["METHOD"]) && $inner["METHOD"]) {
366
            $innerauthmethod = new InnerAuthenticationMethod();
367
            $typeOfInner = "\devices\xml\\" . ($inner["EAP"] ? 'EAPMethod' : 'NonEAPAuthMethod');
368
            $eapmethod = new $typeOfInner();
369
            $eaptype = new Type();
370
            $eaptype->setValue(abs($inner['METHOD']));
371
            $eapmethod->setProperty('Type', $eaptype);
372
            $innerauthmethod->setProperty($typeOfInner, $eapmethod);
373
            return ['inner_method' => $innerauthmethod, 'methodID' => $outerMethod, 'inner_methodID' => $inner['METHOD']];
374
        } else {
375
            return ['inner_method' => 0, 'methodID' => $outerMethod, 'inner_methodID' => 0];
376
        }
377
    }
378
379
    /**
380
     * sets the server-side credentials for a given EAP type
381
     * 
382
     * @param \devices\XML\Type $eaptype the EAP type
383
     * @return \devices\XML\ServerSideCredential
384
     */
385
    private function setServerSideCredentials($eaptype) {
386
        $attr = $this->attributes;
387
        $serversidecredential = new ServerSideCredential();
388
// Certificates and server names
389
        $cAlist = [];
390
        $attrCaList = $attr['internal:CAs'][0];
391
        foreach ($attrCaList as $ca) {
392
            $caObject = new CA();
393
            $caObject->setValue(base64_encode($ca['der']));
394
            $caObject->setAttributes(['format' => 'X.509', 'encoding' => 'base64']);
395
            $cAlist[] = $caObject;
396
        }
397
        $serverids = [];
398
        $servers = $attr['eap:server_name'];
399
        foreach ($servers as $server) {
400
            $serverid = new ServerID();
401
            $serverid->setValue($server);
402
            $serverids[] = $serverid;
403
        }
404
        $serversidecredential->setProperty('EAPType', $eaptype->getValue());
405
        $serversidecredential->setProperty('CA', $cAlist);
406
        $serversidecredential->setProperty('ServerID', $serverids);
407
        return $serversidecredential;
408
    }
409
410
    /**
411
     * sets the realm information for the client-side credential
412
     * 
413
     * @param \devices\XML\ClientSideCredential $clientsidecredential the ClientSideCredential to which the realm info is to be added
414
     * @return void
415
     */
416
    private function setClientSideRealm($clientsidecredential) {
417
        $attr = $this->attributes;
418
        $realm = \core\common\Entity::getAttributeValue($attr, 'internal:realm', 0);
419
        if ($realm === NULL) {
420
            return;
421
        }
422
        if (\core\common\Entity::getAttributeValue($attr, 'internal:verify_userinput_suffix', 0) !== 1) {
423
            return;
424
        }
425
        $clientsidecredential->setProperty('InnerIdentitySuffix', $realm);
426
        if (\core\common\Entity::getAttributeValue($attr, 'internal:hint_userinput_suffix', 0) === 1) {
427
            $clientsidecredential->setProperty('InnerIdentityHint', 'true');
428
        }
429
    }
430
431
    /**
432
     * sets the client certificate
433
     * 
434
     * @return \devices\XML\ClientCertificate
435
     */
436
    private function setClientCertificate() {
437
        $clientCertificateObject = new ClientCertificate();
438
        $clientCertificateObject->setValue(base64_encode($this->clientCert["certdata"]));
439
        $clientCertificateObject->setAttributes(['format' => 'PKCS12', 'encoding' => 'base64']);
440
        return $clientCertificateObject;
441
    }
442
443
    /**
444
     * sets the client-side credentials for the given EAP type
445
     * 
446
     * @param array $eapParams the EAP parameters
447
     * @return \devices\XML\ClientSideCredential
448
     */
449
    private function setClientSideCredentials($eapParams) {
450
        $clientsidecredential = new ClientSideCredential();
451
        $outerId = $this->determineOuterIdString();
452
        if ($outerId !== NULL) {
453
            $clientsidecredential->setProperty('OuterIdentity', $outerId);
454
        }
455
        $this->setClientSideRealm($clientsidecredential);
456
        $clientsidecredential->setProperty('EAPType', $eapParams['inner_methodID'] ? $eapParams['inner_methodID'] : $eapParams['methodID']);
457
458
        // Client Certificate
459
        if ($this->selectedEap == \core\common\EAP::EAPTYPE_SILVERBULLET) {
460
            $clientsidecredential->setProperty('ClientCertificate', $this->setClientCertificate());
461
        }
462
        return $clientsidecredential;
463
    }
464
465
    /**
466
     * sets the EAP method
467
     * 
468
     * @param \devices\XML\Type $eaptype the EAP type XMLObject
469
     * @return \devices\XML\EAPMethod
470
     */
471
    private function setEapMethod($eaptype) {
472
        $eapmethod = new EAPMethod();
473
        $eapmethod->setProperty('Type', $eaptype);
474
        if (isset($this->VendorSpecific)) {
475
            $vendorspecifics = [];
476
            foreach ($this->VendorSpecific as $vs) {
477
                $vendorspecific = new VendorSpecific();
478
                $vs['value']->addAttribute('xsi:noNamespaceSchemaLocation', "xxx.xsd");
479
                $vendorspecific->setValue($vs['value']);
480
                $vendorspecific->setAttributes(['vendor' => $vs['vendor']]);
481
                $vendorspecifics[] = $vendorspecific;
482
            }
483
            $eapmethod->setProperty('VendorSpecific', $vendorspecifics);
484
        }
485
        return($eapmethod);
486
    }
487
488
    /**
489
     * determines the authentication method to use
490
     * 
491
     * @param array $eap the EAP methods, in array representation
492
     * @return \devices\xml\AuthenticationMethod
493
     */
494
    private function getAuthMethod($eap) {
495
        //       $attr = $this->attributes;
496
        $authmethod = new AuthenticationMethod();
497
        $eapParams = $this->getAuthenticationMethodParams($eap);
498
        $eaptype = new Type();
499
        $eaptype->setValue($eapParams['methodID']);
500
// Type
501
        $authmethod->setProperty('EAPMethod', $this->setEapMethod($eaptype));
502
503
// ServerSideCredentials
504
        $authmethod->setProperty('ServerSideCredential', $this->setServerSideCredentials($eaptype));
505
506
// ClientSideCredentials
507
        $authmethod->setProperty('ClientSideCredential', $this->setClientSideCredentials($eapParams));
508
509
        if ($eapParams['inner_method']) {
510
            $authmethod->setProperty('InnerAuthenticationMethod', $eapParams['inner_method']);
511
        }
512
        return $authmethod;
513
    }
514
515
    /**
516
     * 
517
     * @param \SimpleXMLElement   $node   the XML node to marshal
518
     * @param EAPIdentityProvider $object the Object
519
     * @return void
520
     */
521
    private function marshalObject($node, $object) {
522
        $qualClassName = get_class($object);
523
        // remove namespace qualifier
524
        $pos = strrpos($qualClassName, '\\');
525
        $className = substr($qualClassName, $pos + 1);
526
        $name = preg_replace("/_/", "-", $className);
527
        if ($object->getValue()) {
528
            $val = preg_replace('/&/', '&amp;', $object->getValue());
529
            $childNode = $node->addChild($name, $val);
530
        } else {
531
            $childNode = $node->addChild($name);
532
        }
533
        if ($object->areAttributes()) {
534
            $attrs = $object->getAttributes();
535
            foreach ($attrs as $attrt => $attrv) {
536
                $childNode->addAttribute($attrt, $attrv);
537
            }
538
        }
539
        $fields = $object->getAll();
540
        if (empty($fields)) {
541
            return;
542
        }
543
        foreach ($fields as $name => $value) {
544
            if (is_scalar($value)) {
545
                $childNode->addChild($name, strval($value));
546
                continue;
547
            }
548
            if (gettype($value) == 'array') {
549
                foreach ($value as $insideValue) {
550
                    if (is_object($insideValue)) {
551
                        $this->marshalObject($childNode, $insideValue);
552
                    }
553
                }
554
                continue;
555
            }
556
            if (gettype($value) == 'object') {
557
                $this->marshalObject($childNode, $value);
558
            }
559
        }
560
    }
561
562
}
563