Passed
Push — master ( 84d580...39084b )
by Terrence
36:53 queued 21:54
created

IdpList::isSilver()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 3
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace CILogon\Service;
4
5
use CILogon\Service\Util;
6
use DOMDocument;
7
use DOMImplementation;
8
use XSLTProcessor;
9
use SimpleXMLElement;
10
11
/**
12
 * IdpList
13
 *
14
 * This class manages the list of SAML-based IdPs and their
15
 * attributes of interest. Since the InCommon-metadata.xml
16
 * file is rather large and slow to parse using xpath
17
 * queries, this class creates/reads/writes a smaller
18
 * file containing only the IdPs and the few attributes
19
 * needed by the CILogon Service.
20
 *
21
 * When you create a new instance of this class via
22
 * '$idplist = new idplist();', the code first tries to
23
 * read in a previously created idplist file. If no
24
 * such file can be read in successfully, the 'new' method
25
 * then reads in the big InCommon metadata in order to
26
 * create the class idparray and idpdom and write the
27
 * idplist file. You can (re)create the file at any time
28
 * by calling create() (which re-reads the InCommon
29
 * metadata) followed by write() (which writes the idplist
30
 * to file).
31
 *
32
 * Note that this class previously defaulted to writing the idplist
33
 * as an XMl file and then reading that XML file back in. When all
34
 * InCommon and eduGAIN IdPs were 'whitelisted', the xpath queries
35
 * used on the read-in XML file were painfully slow. So now the default
36
 * is to read/write a JSON file and use a protected $idparray which is
37
 * an order of magnitude faster than xpath queries. You can still
38
 * read/write the XML file either by calling the associated read/write
39
 * methods (readXML/writeXML) or by setting $filetype='xml' in the
40
 * contructor. You will probably want to do this for the hourly cronjob
41
 * since the idplist.xml file is known to the world and should be
42
 * updated regularly.
43
 *
44
 * Example usage:
45
 *    require_once 'IdpList.php';
46
 *    // Read in extant idplist.json file, or create one from scratch
47
 *    $idplist = new IdpList();
48
 *    // Rescan InCommon metadata, update IdP list, and write to file
49
 *    $idplist->create();
50
 *    $idplist->setFilename('/tmp/newidplist.json');
51
 *    $idplist->write();
52
 */
53
class IdpList
54
{
55
    /**
56
     * @var DOMDocument $idpdom A DOMDocument which holds the list of IdP
57
     *      entityIDs and their corresponding attributes.
58
     */
59
    protected $idpdom = null;
60
61
    /**
62
     * @var mixed $idparray An array version of $idpdom. It is used
63
     * primarily since searching an array is faster than xpath query.
64
     */
65
    protected $idparray = null;
66
67
    /**
68
     * @var string $idpfilename The name of the IdP list in JSON format.
69
     *      Defaults to DEFAULT_IDP_JSON.
70
     */
71
    protected $idpfilename;
72
73
    /**
74
     * @var string $incommonfilename The name of the InCommon metadata XML
75
     *      file. Defaults to DEFAULT_INCOMMON_XML.
76
     */
77
    protected $incommonfilename;
78
79
    /**
80
     * __construct
81
     *
82
     * Default constructor. This method first attempts to read in an
83
     * existing idplist from a file and store it in the idparray /
84
     * idpdom. If a valid idplist file cannot be read and
85
     * $createfile is true, neW idparray / idpdom is created and
86
     * written to file.
87
     *
88
     * @param string $idpfilename (Optional) The name of the idplist file to
89
     *        read/write. Defaults to DEFAULT_IDP_JSON.
90
     * @param string $incommonfilename (Optional) The name of the InCommon
91
     *        metadata file to read. Defaults to DEFAULT_INCOMMON_XML.
92
     * @param bool $createfile (Optional) Create idplist file if it doesn't
93
     *         exist? Defaults to true.
94
     * @param string $filetype (Optional) The type of file to read/write,
95
     *        one of 'xml' or 'json'. Defaults to 'json'.
96
     */
97
    public function __construct(
98
        $idpfilename = DEFAULT_IDP_JSON,
0 ignored issues
show
Bug introduced by
The constant CILogon\Service\DEFAULT_IDP_JSON was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
99
        $incommonfilename = DEFAULT_INCOMMON_XML,
0 ignored issues
show
Bug introduced by
The constant CILogon\Service\DEFAULT_INCOMMON_XML was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
100
        $createfile = true,
101
        $filetype = 'json'
102
    ) {
103
        $this->setFilename($idpfilename);
104
        $this->setInCommonFilename($incommonfilename);
105
        $result = $this->read($filetype);
106
        if (($result === false) && ($createfile)) {
107
            $this->create();
108
            $this->write($filetype);
109
        }
110
    }
111
112
    /**
113
     * read
114
     *
115
     * This reads in the idplixt file based on the input filetype.
116
     * Defaults to reading in a JSON file.
117
     *
118
     * @param string $filetype (Optional) Type type of file to read, either
119
     *        'xml' or 'json'. Defaults to 'json'.
120
     * @return bool True if the idplist was read from file. False otherwise.
121
     */
122
    public function read($filetype = 'json')
123
    {
124
        if ($filetype == 'xml') {
125
            return $this->readXML();
126
        } elseif ($filetype == 'json') {
127
            return $this->readJSON();
128
        }
129
    }
130
131
    /**
132
     * readXML
133
     *
134
     * This method attempts to read in an existing idplist XML file and
135
     * store its contents in the class $idpdom DOMDocument. It also
136
     * converts the $idpdom to the internal $idparray if not already
137
     * set.
138
     *
139
     * @return bool True if the idplist file was read in correctly.
140
     *         False otherwise.
141
     */
142
    public function readXML()
143
    {
144
        $retval = false;  // Assume read failed
145
146
        $filename = $this->getFilename();
147
        if (
148
            (is_readable($filename)) &&
149
            (($dom = DOMDocument::load($filename, LIBXML_NOBLANKS)) !== false)
0 ignored issues
show
Bug Best Practice introduced by
The method DOMDocument::load() is not static, but was called statically. ( Ignorable by Annotation )

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

149
            (($dom = DOMDocument::/** @scrutinizer ignore-call */ load($filename, LIBXML_NOBLANKS)) !== false)
Loading history...
150
        ) {
151
            $this->idpdom = $dom;
152
            $this->idpdom->preserveWhiteSpace = false;
153
            $this->idpdom->formatOutput = true;
154
            // Convert the read-in DOM to idparray for later use
155
            if (is_null($this->idparray)) {
156
                $this->idparray = $this->DOM2Array($this->idpdom);
157
            }
158
            $retval = true;
159
        } else {
160
            $this->idpdom = null;
161
        }
162
163
        return $retval;
164
    }
165
166
    /**
167
     * readJSON
168
     *
169
     * This method attempts to read in an existing idplist file
170
     * (containing JSON) and store its contents in the class $idparray.
171
     * Note that this does not update the internal $idpdom.
172
     *
173
     * @return bool True if the idplist file was read in correctly.
174
     *         False otherwise.
175
     */
176
    public function readJSON()
177
    {
178
        $retval = false;  // Assume read/json_decode failed
179
180
        $filename = $this->getFilename();
181
        if (
182
            (is_readable($filename)) &&
183
            (($contents = file_get_contents($filename)) !== false) &&
184
            (($tempjson = json_decode($contents, true)) !== null)
185
        ) {
186
            $this->idparray = $tempjson;
187
            $retval = true;
188
        } else {
189
            $this->idparray = null;
190
        }
191
        return $retval;
192
    }
193
194
    /**
195
     * write
196
     *
197
     * This writes out the idplixt file based on the input filetype.
198
     * Defaults to writing a JSON file.
199
     *
200
     * @param string $filetype (Optional) Type type of file to write, either
201
     *        'xml' or 'json'. Defaults to 'json'.
202
     * @return bool True if the idplist was written to file. False
203
     *         otherwise.
204
     */
205
    public function write($filetype = 'json')
206
    {
207
        if ($filetype == 'xml') {
208
            return $this->writeXML();
209
        } elseif ($filetype == 'json') {
210
            return $this->writeJSON();
211
        }
212
    }
213
214
    /**
215
     * writeXML
216
     *
217
     * This method writes the class $idpdom to an XML file. It does
218
     * this by first writing to a temporary file in /tmp, then renaming
219
     * the temp file to the final idplist XML filename. Note that if
220
     * the internal $idpdom does not exist, it attempts to first
221
     * convert the internal $idparray to DOM and then write it.
222
     *
223
     * @return bool True if the idpdom was written to the idplist XML
224
     *         file. False otherwise.
225
     */
226
    public function writeXML()
227
    {
228
        $retval = false; // Assume write failed
229
230
        // If no idpdom, convert idparray to DOM
231
        if (is_null($this->idpdom)) {
232
            $this->idpdom = $this->array2DOM($this->idparray);
233
        }
234
235
        if (!is_null($this->idpdom)) {
236
            $this->idpdom->preserveWhiteSpace = false;
237
            $this->idpdom->formatOutput = true;
238
            $filename = $this->getFilename();
239
            $tmpfname = tempnam('/tmp', 'IDP');
240
            if (
241
                ($this->idpdom->save($tmpfname) > 0) &&
242
                (@rename($tmpfname, $filename))
243
            ) {
244
                chmod($filename, 0664);
245
                $retval = true;
246
            } else {
247
                @unlink($tmpfname);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

247
                /** @scrutinizer ignore-unhandled */ @unlink($tmpfname);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
248
            }
249
        }
250
251
        return $retval;
252
    }
253
254
    /**
255
     * writeJSON
256
     *
257
     * This method writes the class $idparray to a JSON file
258
     * It does this by first writing to a temporary file in /tmp,
259
     * then renaming the temp file to the final idplist JSON filename.
260
     *
261
     * @return bool True if the idparray was written to the idplist
262
     *         JSON file. False otherwise.
263
     */
264
    public function writeJSON()
265
    {
266
        $retval = false; // Assume write failed
267
268
        if (!is_null($this->idparray)) {
269
            $filename = $this->getFilename();
270
            $tmpfname = tempnam('/tmp', 'JSON');
271
            $json = json_encode($this->idparray, JSON_FORCE_OBJECT);
272
            if (
273
                ((file_put_contents($tmpfname, $json)) !== false) &&
274
                (@rename($tmpfname, $filename))
275
            ) {
276
                chmod($filename, 0664);
277
                $retval = true;
278
            } else {
279
                @unlink($tmpfname);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

279
                /** @scrutinizer ignore-unhandled */ @unlink($tmpfname);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
280
            }
281
        }
282
283
        return $retval;
284
    }
285
286
    /**
287
     * addNode
288
     *
289
     * This is a convenience method used by create() to add a new
290
     * child node (such as 'Organization_Name') to a parent idp node.
291
     * It also adds elements to the internal $idparray, thus creating
292
     * the internal idparray at the same time as the idpdom.
293
     *
294
     * @param \DOMDocument $dom A DOMDocument object
295
     * @param \DOMElement $idpnode A pointer to a parent <idp> DOMElement
296
     * @param string $nodename The name of the new child node DOMElement
297
     * @param string $nodevalue The value of the new child node DOMElement
298
     */
299
    private function addNode($dom, $idpnode, $nodename, $nodevalue)
300
    {
301
        $nodename = trim($nodename);    // Remove leading/trailing
302
        $nodevalue = trim($nodevalue);  // spaces, tabs, etc.
303
        $elemnode = $dom->createElement($nodename);
304
        $textnode = $dom->createTextNode($nodevalue);
305
        $elemnode->appendChild($textnode);
306
        $idpnode->appendChild($elemnode);
307
        // Also add element to the internal $idparray for later use
308
        $this->idparray[$idpnode->getAttribute('entityID')][$nodename] =
309
            $nodevalue;
310
    }
311
312
    /**
313
     * sortDOM
314
     *
315
     * This method is called by create() to sort the newly created
316
     * DOMDocument <idp> nodes by Display_Name. It uses an XSL
317
     * transformation to do the work. A new DOMDocument is created
318
     * and returned.
319
     *
320
     * @param DOMDocument $dom A DOMDocument to be sorted by Display_Name
321
     * @return DOMDocument A new DOMDocument with the <idp> elements sorted by
322
     *         Display_Name.
323
     */
324
    private function sortDOM($dom)
325
    {
326
        $xsltsort = <<<EOT
327
            <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
328
                            version="1.0">
329
            <xsl:output method="xml" encoding="UTF-8"/>
330
            <xsl:template match="node() | @*">
331
              <xsl:copy>
332
                <xsl:apply-templates select="node() | @*">
333
                  <xsl:sort select="translate(Display_Name,
334
                      'abcdefghijklmnopqrstuvwxyz',
335
                      'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"
336
                  data-type="text" order="ascending"/>
337
                </xsl:apply-templates>
338
              </xsl:copy>
339
            </xsl:template>
340
            </xsl:stylesheet>
341
EOT;
342
        $xsl = new DOMDocument('1.0');
343
        $xsl->loadXML($xsltsort);
344
        $proc = new XSLTProcessor();
345
        $proc->importStyleSheet($xsl);
346
        $newdom = $proc->transformToDoc($dom);
347
        return $newdom;
348
    }
349
350
    /**
351
     * create
352
     *
353
     * This method is used to populate the class $idpdom DOMDocument
354
     * using information from the InCommon metadata file. Note that
355
     * method updates $idpdom and $idparray. If you want to save either
356
     * to a file, be sure to call write() afterwards.
357
     *
358
     * @return bool True upon successful extraction of IdP information
359
     *         from the InCommon metadata file into the class
360
     *         $idpdom DOMDocument. False otherwise.
361
     */
362
    public function create()
363
    {
364
        $retval = false; // Assume create failed
365
        if (is_readable($this->getInCommonFilename())) {
366
            // Read in the InCommon metadata file
367
            $xmlstr = @file_get_contents($this->getInCommonFilename());
368
            if (strlen($xmlstr) > 0) {
369
                // Need to fix the namespace for Xpath queries to work
370
                $xmlstr = str_replace('xmlns=', 'ns=', $xmlstr);
371
                $xml = new SimpleXMLElement($xmlstr);
372
373
                // Select only IdPs from the InCommon metadata
374
                $result = $xml->xpath(
375
                    "//EntityDescriptor/IDPSSODescriptor" .
376
                    "/ancestor::EntityDescriptor"
377
                );
378
379
                // Create a DOMDocument to build up the list of IdPs.
380
                $dom = DOMImplementation::createDocument(null, 'idps');
0 ignored issues
show
Bug Best Practice introduced by
The method DOMImplementation::createDocument() is not static, but was called statically. ( Ignorable by Annotation )

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

380
                /** @scrutinizer ignore-call */ 
381
                $dom = DOMImplementation::createDocument(null, 'idps');
Loading history...
381
                $idps = $dom->documentElement; // Top level <idps> element
382
383
                // Loop through the IdPs searching for desired attributes
384
                foreach ($result as $idx) {
385
                    // Need to set namespace prefixes for xpath queries to work
386
                    $sxe = $idx[0];
387
                    $sxe->registerXPathNamespace(
388
                        'mdattr',
389
                        'urn:oasis:names:tc:SAML:metadata:attribute'
390
                    );
391
                    $sxe->registerXPathNamespace(
392
                        'saml',
393
                        'urn:oasis:names:tc:SAML:2.0:assertion'
394
                    );
395
                    $sxe->registerXPathNamespace(
396
                        'mdrpi',
397
                        'urn:oasis:names:tc:SAML:metadata:rpi'
398
                    );
399
                    $sxe->registerXPathNamespace(
400
                        'mdui',
401
                        'urn:oasis:names:tc:SAML:metadata:ui'
402
                    );
403
404
                    // Skip any hide-from-discovery entries
405
                    $xp = $sxe->xpath(
406
                        "Extensions/mdattr:EntityAttributes/" .
407
                        "saml:Attribute[@Name='" .
408
                        "http://macedir.org/entity-category']/" .
409
                        "saml:AttributeValue"
410
                    );
411
                    if (($xp !== false) && (count($xp) > 0)) {
412
                        $hide = false;
413
                        foreach ($xp as $value) {
414
                            if ($value == 'http://refeds.org/category/hide-from-discovery') {
415
                                $hide = true;
416
                                break;
417
                            }
418
                        }
419
                        if ($hide) {
420
                            continue;
421
                        }
422
                    }
423
424
                    // Get the entityID of the IdP. Save it for later.
425
                    // The entityID will be the keys of the class idpdom.
426
                    $entityID = '';
427
                    $xp = $idx[0]->xpath('attribute::entityID');
428
                    if (($xp !== false) && (count($xp) > 0)) {
429
                        $entityID = (string)$xp[0]->entityID;
430
                    } else { // No entityID is bad!
431
                        continue;
432
                    }
433
434
                    // Create an <idp> element to hold sub elements
435
                    $idp = $dom->createElement('idp');
436
                    $idp->setAttribute('entityID', $entityID);
437
                    $idps->appendChild($idp);
438
439
                    // Search for the desired <idp> attribute sub-blocks
440
441
                    // Look for OrganizationDisplayName and mdui:DisplayName.
442
                    $Organization_Name = '';
443
                    $Display_Name = '';
444
445
                    $xp = $idx[0]->xpath(
446
                        "Organization/OrganizationDisplayName[starts-with(@xml:lang,'en')]"
447
                    );
448
                    if (($xp !== false) && (count($xp) > 0)) {
449
                        $Organization_Name = (string)$xp[0];
450
                    }
451
452
                    $xp = $sxe->xpath(
453
                        "IDPSSODescriptor/Extensions/mdui:UIInfo/mdui:DisplayName[starts-with(@xml:lang,'en')]"
454
                    );
455
                    if (($xp !== false) && (count($xp) > 0)) {
456
                        $Display_Name = (string)$xp[0];
457
                    }
458
459
                    // If neither OrganizationDisplayName nor mdui:DisplayName
460
                    // was found, then use the entityID as a last resort.
461
                    if (
462
                        (strlen($Organization_Name) == 0) &&
463
                        (strlen($Display_Name) == 0)
464
                    ) {
465
                        $Organization_Name = $entityID;
466
                        $Display_Name = $entityID;
467
                    }
468
469
                    // Add nodes for both Organization_Name and Display_Name,
470
                    // using the value of the other if one is empty.
471
                    $this->addNode(
472
                        $dom,
473
                        $idp,
474
                        'Organization_Name',
475
                        ((strlen($Organization_Name) > 0) ?
476
                            $Organization_Name :
477
                            $Display_Name)
478
                    );
479
                    $this->addNode(
480
                        $dom,
481
                        $idp,
482
                        'Display_Name',
483
                        ((strlen($Display_Name) > 0) ?
484
                            $Display_Name :
485
                            $Organization_Name)
486
                    );
487
488
                    $xp = $idx[0]->xpath('Organization/OrganizationURL');
489
                    if (($xp !== false) && (count($xp) > 0)) {
490
                        $this->addNode($dom, $idp, 'Home_Page', (string)$xp[0]);
491
                    }
492
493
                    $name = '';
494
                    $xp = $idx[0]->xpath(
495
                        "ContactPerson[@contactType='support']/GivenName"
496
                    );
497
                    if (($xp !== false) && (count($xp) > 0)) {
498
                        $name = (string)$xp[0];
499
                    }
500
                    $xp = $idx[0]->xpath(
501
                        "ContactPerson[@contactType='support']/SurName"
502
                    );
503
                    if (($xp !== false) && (count($xp) > 0)) {
504
                        $name .= ((strlen($name) > 0) ? ' ' : '') .
505
                            (string)($xp[0]);
506
                    }
507
                    if (strlen($name) > 0) {
508
                        $this->addNode($dom, $idp, 'Support_Name', $name);
509
                    }
510
511
                    $xp = $idx[0]->xpath(
512
                        "ContactPerson[@contactType='support']/EmailAddress"
513
                    );
514
                    if (($xp !== false) && (count($xp) > 0)) {
515
                        $this->addNode(
516
                            $dom,
517
                            $idp,
518
                            'Support_Address',
519
                            (string)$xp[0]
520
                        );
521
                    }
522
523
                    $name = '';
524
                    $xp = $idx[0]->xpath(
525
                        "ContactPerson[@contactType='technical']/GivenName"
526
                    );
527
                    if (($xp !== false) && (count($xp) > 0)) {
528
                        $name = (string)$xp[0];
529
                    }
530
                    $xp = $idx[0]->xpath(
531
                        "ContactPerson[@contactType='technical']/SurName"
532
                    );
533
                    if (($xp !== false) && (count($xp) > 0)) {
534
                        $name .= ((strlen($name) > 0) ? ' ' : '') .
535
                            (string)($xp[0]);
536
                    }
537
                    if (strlen($name) > 0) {
538
                        $this->addNode($dom, $idp, 'Technical_Name', $name);
539
                    }
540
541
                    $xp = $idx[0]->xpath(
542
                        "ContactPerson[@contactType='technical']/EmailAddress"
543
                    );
544
                    if (($xp !== false) && (count($xp) > 0)) {
545
                        $this->addNode(
546
                            $dom,
547
                            $idp,
548
                            'Technical_Address',
549
                            (string)$xp[0]
550
                        );
551
                    }
552
553
                    $name = '';
554
                    $xp = $idx[0]->xpath(
555
                        "ContactPerson[@contactType='administrative']/GivenName"
556
                    );
557
                    if (($xp !== false) && (count($xp) > 0)) {
558
                        $name = (string)$xp[0];
559
                    }
560
                    $xp = $idx[0]->xpath(
561
                        "ContactPerson[@contactType='administrative']/SurName"
562
                    );
563
                    if (($xp !== false) && (count($xp) > 0)) {
564
                        $name .= ((strlen($name) > 0) ? ' ' : '') .
565
                            (string)($xp[0]);
566
                    }
567
                    if (strlen($name) > 0) {
568
                        $this->addNode($dom, $idp, 'Administrative_Name', $name);
569
                    }
570
571
                    $xp = $idx[0]->xpath(
572
                        "ContactPerson[@contactType='administrative']/EmailAddress"
573
                    );
574
                    if (($xp !== false) && (count($xp) > 0)) {
575
                        $this->addNode(
576
                            $dom,
577
                            $idp,
578
                            'Administrative_Address',
579
                            (string)$xp[0]
580
                        );
581
                    }
582
583
                    // Check for assurance-certification = silver, bronze, or SIRTFI
584
                    $xp = $sxe->xpath(
585
                        "Extensions/mdattr:EntityAttributes/" .
586
                        "saml:Attribute[@Name='" .
587
                        "urn:oasis:names:tc:SAML:attribute:" .
588
                        "assurance-certification']/saml:AttributeValue"
589
                    );
590
                    if (($xp !== false) && (count($xp) > 0)) {
591
                        foreach ($xp as $value) {
592
                            if ($value == 'http://id.incommon.org/assurance/silver') {
593
                                $this->addNode($dom, $idp, 'Silver', '1');
594
                            } elseif ($value == 'http://id.incommon.org/assurance/bronze') {
595
                                $this->addNode($dom, $idp, 'Bronze', '1');
596
                            } elseif ($value == 'https://refeds.org/sirtfi') {
597
                                $this->addNode($dom, $idp, 'SIRTFI', '1');
598
                            }
599
                        }
600
                    }
601
602
                    // Check for registered-by-incommon
603
                    $xp = $sxe->xpath(
604
                        "Extensions/mdattr:EntityAttributes/" .
605
                        "saml:Attribute[@Name='" .
606
                        "http://macedir.org/entity-category']/" .
607
                        "saml:AttributeValue"
608
                    );
609
                    if (($xp !== false) && (count($xp) > 0)) {
610
                        foreach ($xp as $value) {
611
                            if ($value == 'http://id.incommon.org/category/registered-by-incommon') {
612
                                $this->addNode(
613
                                    $dom,
614
                                    $idp,
615
                                    'Registered_By_InCommon',
616
                                    '1'
617
                                );
618
                                break;
619
                            }
620
                        }
621
                    }
622
623
                    // Check for research-and-scholarship
624
                    $xp = $sxe->xpath(
625
                        "Extensions/mdattr:EntityAttributes/" .
626
                        "saml:Attribute[@Name='" .
627
                        "http://macedir.org/entity-category-support']/" .
628
                        "saml:AttributeValue"
629
                    );
630
                    if (($xp !== false) && (count($xp) > 0)) {
631
                        $addedrands = false;
632
                        $incommonrands = false;
633
                        $refedsrands = false;
634
                        foreach ($xp as $value) {
635
                            if ($value == 'http://id.incommon.org/category/research-and-scholarship') {
636
                                $incommonrands = true;
637
                                $this->addNode($dom, $idp, 'InCommon_RandS', '1');
638
                            }
639
                            if ($value == 'http://refeds.org/category/research-and-scholarship') {
640
                                $refedsrands = true;
641
                                $this->addNode($dom, $idp, 'REFEDS_RandS', '1');
642
                            }
643
                            if (
644
                                (!$addedrands) &&
645
                                ($incommonrands || $refedsrands)
646
                            ) {
647
                                $addedrands = true;
648
                                $this->addNode($dom, $idp, 'RandS', '1');
649
                            }
650
                        }
651
                    }
652
653
                    // CIL-558 Check for <SingleLogoutService>
654
                    $Logout = '';
655
                    // First, check for HTTP-Redirect version
656
                    $xp = $sxe->xpath("IDPSSODescriptor/SingleLogoutService" .
657
                        "[@Binding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect']");
658
                    if (($xp !== false) && (count($xp) > 0)) {
659
                        $Logout = (string)($xp[0]->attributes())['Location'];
660
                    }
661
                    // If no HTTP-Redirect, check for HTTP-POST
662
                    if (empty($Logout)) {
663
                        $xp = $sxe->xpath("IDPSSODescriptor/SingleLogoutService" .
664
                            "[@Binding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST']");
665
                        if (($xp !== false) && (count($xp) > 0)) {
666
                            $Logout = (string)($xp[0]->attributes())['Location'];
667
                        }
668
                    }
669
                    // Finally, a hack for Shibboleth-based IdPs.
670
                    // Check for <SingleSignOnService> HTTP-Redirect
671
                    // and regex for the built-in Simple Logout URL.
672
                    if (empty($Logout)) {
673
                        $xp = $sxe->xpath("IDPSSODescriptor/SingleSignOnService" .
674
                            "[@Binding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect']");
675
                        if (($xp !== false) && (count($xp) > 0)) {
676
                            $tmp = (string)($xp[0]->attributes())['Location'];
677
                            if (preg_match('|^(.*)/profile/|', $tmp, $matches)) {
678
                                $Logout = $matches[1] . '/profile/Logout';
679
                            }
680
                        }
681
                    }
682
                    if (!empty($Logout)) {
683
                        // If Shib IdP, transform URL into Simple Logout URL
684
                        // https://wiki.shibboleth.net/confluence/x/AAJSAQ
685
                        if (preg_match('|^(.*)/profile/|', $Logout, $matches)) {
686
                            $Logout = $matches[1] . '/profile/Logout';
687
                        }
688
                        $this->addNode($dom, $idp, 'Logout', $Logout);
689
                    }
690
691
                    // Add a <Whitelisted> block for all IdPs
692
                    // not in the BLACKLIST_IDP_ARRAY.
693
                    if (!in_array($entityID, BLACKLIST_IDP_ARRAY)) {
0 ignored issues
show
Bug introduced by
The constant CILogon\Service\BLACKLIST_IDP_ARRAY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
694
                        $this->addNode($dom, $idp, 'Whitelisted', '1');
695
                    }
696
                }
697
698
                // Read in any test IdPs and add them to the list
699
                if (
700
                    (defined('TEST_IDP_XML')) &&
701
                    (!empty(TEST_IDP_XML)) &&
0 ignored issues
show
Bug introduced by
The constant CILogon\Service\TEST_IDP_XML was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
702
                    (is_readable(TEST_IDP_XML)) &&
703
                    (($dom2 = DOMDocument::load(TEST_IDP_XML)) !== false)
0 ignored issues
show
Bug Best Practice introduced by
The method DOMDocument::load() is not static, but was called statically. ( Ignorable by Annotation )

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

703
                    (($dom2 = DOMDocument::/** @scrutinizer ignore-call */ load(TEST_IDP_XML)) !== false)
Loading history...
704
                ) {
705
                    $idpnodes = $dom2->getElementsByTagName('idp');
706
                    foreach ($idpnodes as $idpnode) {
707
                        // Check if the entityID already exists. If so,
708
                        // delete it from both the idps DOM and the idparray
709
                        // and instead add the one from the testidplist.
710
                        $entityID = $idpnode->attributes->item(0)->value;
711
                        if (array_key_exists($entityID, $this->idparray)) {
712
                            // Easy - simply delete the array entry for the
713
                            // existing entityID
714
                            unset($this->idparray[$entityID]);
715
716
                            // Hard - search through the current DOM for a
717
                            // matching entityID to get the DOMNode, which
718
                            // can then be removed from the DOM.
719
                            $curridpnodes = $dom->getElementsByTagName('idp');
720
                            foreach ($curridpnodes as $curridpnode) {
721
                                $currEntityID =
722
                                    $curridpnode->attributes->item(0)->value;
723
                                if ($currEntityID == $entityID) {
724
                                    $idps->removeChild($curridpnode);
725
                                    break;
726
                                }
727
                            }
728
                        }
729
730
                        // Add the new idp node to the DOM
731
                        $node = $dom->importNode($idpnode, true);
732
                        $idps->appendChild($node);
733
734
                        // Add the testidplist nodes to the $idparray
735
                        foreach ($node->childNodes as $child) {
736
                            if ($child->nodeName != '#text') {
737
                                $this->idparray[$entityID][$child->nodeName] =
738
                                    $child->nodeValue;
739
                            }
740
                        }
741
                    }
742
                }
743
744
                // Sort the DOMDocument and idparray by Display_Name
745
                $this->idpdom = $this->sortDOM($dom);
746
                uasort($this->idparray, function ($a, $b) {
747
                    return strcasecmp(
748
                        $a['Display_Name'],
749
                        $b['Display_Name']
750
                    );
751
                });
752
753
                $retval = true;
754
            }
755
        }
756
757
        return $retval;
758
    }
759
760
    /**
761
     * getFilename
762
     *
763
     * This function returns a string of the full path of the IdP list
764
     * filename.  See also setFilename().
765
     *
766
     * @return string The IdP list filename.
767
     */
768
    public function getFilename()
769
    {
770
        return $this->idpfilename;
771
    }
772
773
    /**
774
     * setFilename
775
     *
776
     * This function sets the string of the full path of the IdP list
777
     * filename.  See also getFilename().
778
     *
779
     * @param string $filename he new name of the IdP list filename.
780
     */
781
    public function setFilename($filename)
782
    {
783
        $this->idpfilename = $filename;
784
    }
785
786
    /**
787
     * getInCommonFilename
788
     *
789
     * This function returns a string of the full path of the InCommon
790
     * metadata filename.  See also setInCommonFilename().
791
     *
792
     * @return string The InCommon metadata filename.
793
     */
794
    public function getInCommonFilename()
795
    {
796
        return $this->incommonfilename;
797
    }
798
799
    /**
800
     * setInCommonFilename
801
     *
802
     * This function sets the string of the full path of the InCommon
803
     * metadata filename.  See also getInCommonFilename().
804
     *
805
     * @param string $filename The new name of the InCommon metadata filename.
806
     */
807
    public function setInCommonFilename($filename)
808
    {
809
        $this->incommonfilename = $filename;
810
    }
811
812
    /**
813
     * getEntityIDs
814
     *
815
     * This method returns the entityIDs of the idplist as an array.
816
     *
817
     * @return array An array of the entityIDs
818
     */
819
    public function getEntityIDs()
820
    {
821
        $retarr = array();
822
        if (is_array($this->idparray)) {
823
            $retarr = array_keys($this->idparray);
824
        }
825
        return $retarr;
826
    }
827
828
    /**
829
     * getOrganizationName
830
     *
831
     * This function returns the Organization_Name of the selected
832
     * $entityID.
833
     *
834
     * @param string $entityID The entityID to search for
835
     * @return string The Organization_Name for the $entityID. Return
836
     *         string is empty if no matching $entityID found.
837
     */
838
    public function getOrganizationName($entityID)
839
    {
840
        $retval = '';
841
        if (isset($this->idparray[$entityID]['Organization_Name'])) {
842
            $retval = $this->idparray[$entityID]['Organization_Name'];
843
        }
844
        return $retval;
845
    }
846
847
    /**
848
     * getDisplayName
849
     *
850
     * This function returns the Display_Name of the selected
851
     * $entityID.
852
     *
853
     * @param string $entityID The entityID to search for
854
     * @return string The Display_Name for the $entityID. Return
855
     *         string is empty if no matching $entityID found.
856
     */
857
    public function getDisplayName($entityID)
858
    {
859
        $retval = '';
860
        if (isset($this->idparray[$entityID]['Display_Name'])) {
861
            $retval = $this->idparray[$entityID]['Display_Name'];
862
        }
863
        return $retval;
864
    }
865
866
    /**
867
     * getLogout
868
     *
869
     * This function returns the Logout URL of the selected $entityID.
870
     *
871
     * @param string $entityID The entityID to search for
872
     * @return string The Logout  URLfor the $entityID. Return
873
     *         string is empty if no matching $entityID found.
874
     */
875
    public function getLogout($entityID)
876
    {
877
        $retval = '';
878
        if (isset($this->idparray[$entityID]['Logout'])) {
879
            $retval = $this->idparray[$entityID]['Logout'];
880
        }
881
        return $retval;
882
    }
883
884
    /**
885
     * entityIDExists
886
     *
887
     * This function searchs for the given idp entityID.
888
     *
889
     * @param string $entityID The entityID to search for
890
     * @return bool True if the given entityID is found. False otherwise.
891
     */
892
    public function entityIDExists($entityID)
893
    {
894
        return (isset($this->idparray[$entityID]));
895
    }
896
897
    /**
898
     * exists
899
     *
900
     * This is simply a convenience function for entityIDExists.
901
     *
902
     * @param string $entityID The enityID to search for
903
     * @return bool True if the given entityID is found. False otherwise.
904
     */
905
    public function exists($entityID)
906
    {
907
        return $this->entityIDExists($entityID);
908
    }
909
910
    /**
911
     * isAttributeSet
912
     *
913
     * This function checks if the passed-in $attr is set to '1' for
914
     * the entityID, and returns true if so.
915
     *
916
     * @param string $entityID The enityID to search for.
917
     * @param string $attr The attribute in question.
918
     * @return bool True if the given attribute is '1' for the entityID.
919
     *         False otherwise.
920
     */
921
    public function isAttributeSet($entityID, $attr)
922
    {
923
        return (isset($this->idparray[$entityID][$attr]) &&
924
                     ($this->idparray[$entityID][$attr] == 1));
925
    }
926
927
    /**
928
     * isWhitelisted
929
     *
930
     * This method searches for the given entityID and checks if the
931
     *'Whitelisted' entry has been set to '1'.
932
     *
933
     * @param string $entityID The enityID to search for
934
     * @return bool True if the given entityID is marked 'Whitelisted'.
935
     *         False otherwise.
936
     */
937
    public function isWhitelisted($entityID)
938
    {
939
        return $this->isAttributeSet($entityID, 'Whitelisted');
940
    }
941
942
    /**
943
     * isSilver
944
     *
945
     * This method searches for the given entityID and checks if the
946
     *'Silver' entry has been set to '1'.
947
     *
948
     * @param string $entityID The enityID to search for
949
     * @return bool True if the given entityID is certified 'Silver'.
950
     *         False otherwise.
951
     */
952
    public function isSilver($entityID)
953
    {
954
        return $this->isAttributeSet($entityID, 'Silver');
955
    }
956
957
    /**
958
     * isBronze
959
     *
960
     * This method searches for the given entityID and checks if the
961
     *'Bronze' entry has been set to '1'.
962
     *
963
     * @param string $entityID The enityID to search for
964
     * @return bool True if the given entityID is certified 'Bronze'.
965
     *         False otherwise.
966
     */
967
    public function isBronze($entityID)
968
    {
969
        return $this->isAttributeSet($entityID, 'Bronze');
970
    }
971
972
    /**
973
     * isRandS
974
     *
975
     * This method searches for the given entityID and checks if the
976
     *'RandS' entry has been set to '1'.
977
     *
978
     * @param string $entityID The enityID to search for
979
     * @return bool True if the given entityID is listed as 'RandS'
980
     *         (research-and-scholarship). False otherwise.
981
     */
982
    public function isRandS($entityID)
983
    {
984
        return $this->isAttributeSet($entityID, 'RandS');
985
    }
986
987
    /**
988
     * isInCommonRandS
989
     *
990
     * This method searches for the given entityID and checks if the
991
     *'InCommon_RandS' entry has been set to '1'.
992
     *
993
     * @param string $entityID The enityID to search for
994
     * @return bool True if the given entityID is listed as
995
     *        'InCommon_RandS'. False otherwise.
996
     */
997
    public function isInCommonRandS($entityID)
998
    {
999
        return $this->isAttributeSet($entityID, 'InCommon_RandS');
1000
    }
1001
1002
    /**
1003
     * isREFEDSRandS
1004
     *
1005
     * This method searches for the given entityID and checks if the
1006
     *'REFEDS_RandS' entry has been set to '1'.
1007
     *
1008
     * @param string $entityID The enityID to search for
1009
     * @return bool True if the given entityID is listed as
1010
     *         'REFEDS_RandS'. False otherwise.
1011
     */
1012
    public function isREFEDSRandS($entityID)
1013
    {
1014
        return $this->isAttributeSet($entityID, 'REFEDS_RandS');
1015
    }
1016
1017
    /**
1018
     * isRegisteredByInCommon
1019
     *
1020
     * This method searches for the given entityID and checks if the
1021
     *'Registered_By_InCommon' entry has been set to '1'.
1022
     *
1023
     * @param string $entityID The enityID to search for
1024
     * @return bool True if the given entityID is listed as
1025
     *         'Registered_By_InCommon'. False otherwise.
1026
     */
1027
    public function isRegisteredByInCommon($entityID)
1028
    {
1029
        return $this->isAttributeSet($entityID, 'Registered_By_InCommon');
1030
    }
1031
1032
    /**
1033
     * isSIRTFI
1034
     *
1035
     * This method searches for the given entityID and checks if the
1036
     *'SIRTFI' entry has been set to '1'.
1037
     *
1038
     * @param string $entityID The enityID to search for
1039
     * @return bool True if the given entityID is listed as
1040
     *         SIRTFI. False otherwise.
1041
     */
1042
    public function isSIRTFI($entityID)
1043
    {
1044
        return $this->isAttributeSet($entityID, 'SIRTFI');
1045
    }
1046
1047
    /**
1048
     * getSAMLIdPs
1049
     *
1050
     * This method returns a two-dimensional array of SAML-based IdPs.
1051
     * The primary key of the array is the entityID, the secondary key is
1052
     * either 'Organization_Name' (corresponds to OrganizationDisplayName)
1053
     * or 'Display_Name' (corresponds to mdui:DisplayName).
1054
     * If a non-null parameter is passed in it returns a subset of the
1055
     * InCommon IdPs. 0 means list only non-whitelisted IdPs, 1 means list
1056
     * only whitelisted IdPs, 2 means list only R&S IdPs.
1057
     *
1058
     * @param int $filter
1059
     *        null => all SAML-based IdPs
1060
     *        0    => non-whitelisted SAML-based IdPs
1061
     *        1    => whitelisted SAML-based IdPs
1062
     *        2    => R&S SAML-based IdPs
1063
     *        3    => whitelisted "Registered By InCommon" IdPs
1064
     * $return array An array of SAML-based IdP Organization Names and Display
1065
     *         Names, possibly filtered by whitelisted / non-whitelisted / R&S.
1066
     */
1067
    public function getSAMLIdPs($filter = null)
1068
    {
1069
        $retarr = array();
1070
1071
        foreach ($this->idparray as $key => $value) {
1072
            if (
1073
                (!is_null($filter)) &&
1074
                (($filter === 0) &&
1075
                 ($this->isWhitelisted($key))) ||
1076
                (($filter === 1) &&
1077
                 (!$this->isWhitelisted($key))) ||
1078
                (($filter === 2) &&
1079
                 (!$this->isRandS($key))) ||
1080
                (($filter === 3) &&
1081
                 (!$this->isRegisteredByInCommon($key)) ||
1082
                 (!$this->isWhitelisted($key)))
1083
            ) {
1084
                continue;
1085
            }
1086
            $retarr[$key]['Organization_Name'] = $this->idparray[$key]['Organization_Name'];
1087
            $retarr[$key]['Display_Name'] = $this->idparray[$key]['Display_Name'];
1088
        }
1089
1090
        return $retarr;
1091
    }
1092
1093
    /**
1094
     * getNonWhitelistedIdPs
1095
     *
1096
     * This method returns an array of non-whitelisted IdPs where the
1097
     * keys of the array are the entityIDs and the values are the
1098
     * pretty print Organization Names. Note that this essentially
1099
     * returns the IdPs in the BLACKLIST_IDP_ARRAY.
1100
     *
1101
     * @return array An array of non-whitelisted IdPs.
1102
     */
1103
    public function getNonWhitelistedIdPs()
1104
    {
1105
        return $this->getSAMLIdPs(0);
1106
    }
1107
1108
    /**
1109
     * getWhitelistedIdPs
1110
     *
1111
     * This method returns an array of whitelisted IdPs where the keys
1112
     * of the array are the entityIDs and the values are the
1113
     * pretty print Organization Names. Note that this returns all of the
1114
     * IdPs not in the BLACKLIST_IDP_ARRAY.
1115
     *
1116
     * @return array An array of whitelisted IdPs.
1117
     */
1118
    public function getWhitelistedIdPs()
1119
    {
1120
        return $this->getSAMLIdPs(1);
1121
    }
1122
1123
    /**
1124
     * getRandSIdPs
1125
     *
1126
     * This method returns an array of R&S IdPs where the keys
1127
     * of the array are the entityIDs and the values are the
1128
     * pretty print Organization Names.
1129
     *
1130
     * @return array An array of Research and Scholarship (R&S) IdPs.
1131
     */
1132
    public function getRandSIdPs()
1133
    {
1134
        return $this->getSAMLIdPs(2);
1135
    }
1136
1137
    /**
1138
     * getRegisteredByInCommonIdPs
1139
     *
1140
     * This method returns an array of IdPs that have been tagged as
1141
     * "Registered_By_InCommon". The keys of the array are the entityIDs
1142
     * and the values are the pretty print Organization Names.
1143
     *
1144
     * @return array An array of Research and Scholarship (R&S) IdPs.
1145
     */
1146
    public function getRegisteredByInCommonIdPs()
1147
    {
1148
        return $this->getSAMLIdPs(3);
1149
    }
1150
1151
    /**
1152
     * getShibInfo
1153
     *
1154
     * This function returns an array with two types of Shibboleth
1155
     * information.  The first set of info is specific to the user's
1156
     * current Shibboleth session, such as REMOTE_USER. The second set
1157
     * of info reads info from the passed-in metadata file specific to
1158
     * the IdP, such as the pretty-print name of the IdP.
1159
     *
1160
     * @param string $entityID (Optional) The entityID to search for in
1161
     *        the InCommon metadata. Defaults to the HTTP header
1162
     *        HTTP_SHIB_IDENTITY_PROVIDER.
1163
     * @return array  An array containing the various shibboleth
1164
     *         attributes for the current Shibboleth session. The
1165
     *         keys of the array are 'pretty print' names of the
1166
     *         various attribute value names (such as
1167
     *         'User Identifier' for REMOTE_USER) and the values
1168
     *         of the array are the actual Shibboleth session values.
1169
     */
1170
    public function getShibInfo($entityID = '')
1171
    {
1172
        $shibarray = array();  // Array to be returned
1173
1174
        // Set the blob set of info, namely those shib attributes which
1175
        // were given by the IdP when the user authenticated.
1176
        if (strlen($entityID) == 0) {
1177
            $entityID = Util::getServerVar('HTTP_SHIB_IDENTITY_PROVIDER');
1178
        }
1179
        // CIL-254 - For LIGO backup IdPs, remap entityID to the main IdP
1180
        if (
1181
            preg_match(
1182
                '%(https://login)[^\.]*(.ligo.org/idp/shibboleth)%',
1183
                $entityID,
1184
                $matches
1185
            )
1186
        ) {
1187
            $entityID = $matches[1] . $matches[2];
1188
        }
1189
        $shibarray['Identity Provider'] = $entityID;
1190
        $shibarray['User Identifier'] = Util::getServerVar('REMOTE_USER');
1191
        $shibarray['ePPN'] = Util::getServerVar('HTTP_EPPN');
1192
        $shibarray['ePTID'] = Util::getServerVar('HTTP_PERSISTENT_ID');
1193
        $shibarray['First Name'] = Util::getServerVar('HTTP_GIVENNAME');
1194
        $shibarray['Last Name'] = Util::getServerVar('HTTP_SN');
1195
        $shibarray['Display Name'] = Util::getServerVar('HTTP_DISPLAYNAME');
1196
        $shibarray['Email Address'] = Util::getServerVar('HTTP_MAIL');
1197
        $shibarray['Level of Assurance'] = Util::getServerVar('HTTP_ASSURANCE');
1198
        $shibarray['Affiliation'] = Util::getServerVar('HTTP_AFFILIATION');
1199
        $shibarray['OU'] = Util::getServerVar('HTTP_OU');
1200
        $shibarray['Member'] = Util::getServerVar('HTTP_MEMBER');
1201
        $shibarray['Authn Context'] = Util::getServerVar('HTTP_SHIB_AUTHNCONTEXT_CLASS');
1202
        $shibarray['Entitlement'] = Util::getServerVar('HTTP_ENTITLEMENT');
1203
        $shibarray['iTrustUIN'] = Util::getServerVar('HTTP_ITRUSTUIN');
1204
1205
        // Make sure to use only the first of multiple values.
1206
        $attrs = array('ePPN','ePTID','First Name','Last Name',
1207
                       'Display Name','Email Address');
1208
        foreach ($attrs as $attr) {
1209
            if (($pos = strpos($shibarray[$attr], ';')) !== false) {
1210
                $shibarray[$attr] = substr($shibarray[$attr], 0, $pos);
1211
            }
1212
        }
1213
1214
        // Next, read the attributes for the given IdP. This includes
1215
        // values such as the display name for the IdP, the home page
1216
        // of the organization, and contact information.
1217
        $attrarray = array(
1218
            'Organization_Name',
1219
            'Home_Page',
1220
            'Support_Name',
1221
            'Support_Address',
1222
            'Technical_Name',
1223
            'Technical_Address',
1224
            'Administrative_Name',
1225
            'Administrative_Address'
1226
        );
1227
1228
        foreach ($attrarray as $attr) {
1229
            if (isset($this->idparray[$entityID][$attr])) {
1230
                $shibarray[preg_replace('/_/', ' ', $attr)] =
1231
                    $this->idparray[$entityID][$attr];
1232
            }
1233
        }
1234
1235
        return $shibarray;
1236
    }
1237
1238
    /**
1239
     * DOM2Array
1240
     *
1241
     * This function sorts the passed-in DOM corresponding to
1242
     * idplist.xml and returns a 2D array where the keys are entityIDs
1243
     * and the values are arrays of attributes for each IdP.
1244
     *
1245
     * @param DOMDocument The DOM containing the list of IdPs to convert to
0 ignored issues
show
Bug introduced by
The type CILogon\Service\The 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...
1246
     *        an array. Returns null on error.
1247
     * @return array An array corresponding to the DOM of the IdPs.
1248
     */
1249
    public function DOM2Array($dom)
1250
    {
1251
        $retarr = null;
1252
1253
        if (!is_null($dom)) {
1254
            foreach ($dom->childNodes as $idps) {
1255
                // Top-level DOM has 'idps' only
1256
                foreach ($idps->childNodes as $idp) {
1257
                    // Loop through each <idp> element
1258
                    $entityID = $idp->attributes->item(0)->value;
1259
                    foreach ($idp->childNodes as $attr) {
1260
                        // Get all sub-attributes of the current <idp>
1261
                        if ($attr->nodeName != '#text') {
1262
                            $retarr[$entityID][$attr->nodeName] = $attr->nodeValue;
1263
                        }
1264
                    }
1265
                }
1266
            }
1267
        }
1268
1269
        return $retarr;
1270
    }
1271
1272
    /**
1273
     * array2DOM
1274
     *
1275
     * This function takes an array of IdPs (such as idparray) and
1276
     * returns a corresponding DOM which can be written to XML.
1277
     *
1278
     * @param array $arr An array corresponding to the idplist.
1279
     * @return DOMDocument A DOM for the idplist which can be written to XML.
1280
     */
1281
    public function array2DOM($arr)
1282
    {
1283
        $retdom = null;
1284
1285
        if (!is_null($arr)) {
0 ignored issues
show
introduced by
The condition is_null($arr) is always false.
Loading history...
1286
            $dom = DOMImplementation::createDocument(null, 'idps');
0 ignored issues
show
Bug Best Practice introduced by
The method DOMImplementation::createDocument() is not static, but was called statically. ( Ignorable by Annotation )

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

1286
            /** @scrutinizer ignore-call */ 
1287
            $dom = DOMImplementation::createDocument(null, 'idps');
Loading history...
1287
            $idps = $dom->documentElement; // Top level <idps> element
1288
1289
            foreach ($arr as $entityID => $attrs) {
1290
                // Create an <idp> element to hold sub elements
1291
                $idp = $dom->createElement('idp');
1292
                $idp->setAttribute('entityID', $entityID);
1293
                $idps->appendChild($idp);
1294
                foreach ($attrs as $attr => $value) {
1295
                    $this->addNode($dom, $idp, $attr, $value);
1296
                }
1297
            }
1298
            $retdom = $this->sortDOM($dom);
1299
        }
1300
1301
        return $retdom;
1302
    }
1303
}
1304