Passed
Push — master ( 4cefa8...0d1faa )
by Tim
02:17 queued 32s
created

Aggregator::setFilters()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 42
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 21
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 42
rs 9.584
1
<?php
2
3
namespace SimpleSAML\Module\aggregator2;
4
5
use \SimpleSAML\Error\Exception;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Error\Exception was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
6
use \SimpleSAML\Configuration;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Configuration 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...
7
use \SimpleSAML\Logger;
8
use \SimpleSAML\Utils\System;
9
10
use \SAML2\Constants;
11
use \SAML2\SignedElement;
12
use \SAML2\Utils;
13
use \SAML2\XML\md\EntitiesDescriptor;
14
use \SAML2\XML\md\EntityDescriptor;
15
use \SAML2\XML\mdrpi\RegistrationInfo;
16
17
use \RobRichards\XMLSecLibs\XMLSecurityKey;
18
19
/**
20
 * Class which implements a basic metadata aggregator.
21
 *
22
 * @package SimpleSAMLphp
23
 */
24
class Aggregator
25
{
26
    /**
27
     * The list of signature algorithms supported by the aggregator.
28
     *
29
     * @var array
30
     */
31
    public static $SUPPORTED_SIGNATURE_ALGORITHMS = [
32
        XMLSecurityKey::RSA_SHA1,
33
        XMLSecurityKey::RSA_SHA256,
34
        XMLSecurityKey::RSA_SHA384,
35
        XMLSecurityKey::RSA_SHA512,
36
    ];
37
38
    /**
39
     * The ID of this aggregator.
40
     *
41
     * @var string
42
     */
43
    protected $id;
44
45
    /**
46
     * Our log "location".
47
     *
48
     * @var string
49
     */
50
    protected $logLoc;
51
52
    /**
53
     * Which cron-tag this should be updated in.
54
     *
55
     * @var string|null
56
     */
57
    protected $cronTag;
58
59
    /**
60
     * Absolute path to a cache directory.
61
     *
62
     * @var string|null
63
     */
64
    protected $cacheDirectory;
65
66
    /**
67
     * The entity sources.
68
     *
69
     * Array of sspmod_aggregator2_EntitySource objects.
70
     *
71
     * @var array
72
     */
73
    protected $sources = [];
74
75
    /**
76
     * How long the generated metadata should be valid, as a number of seconds.
77
     *
78
     * This is used to set the validUntil attribute on the generated EntityDescriptor.
79
     *
80
     * @var int
81
     */
82
    protected $validLength;
83
84
    /**
85
     * Duration we should cache generated metadata.
86
     *
87
     * @var int
88
     */
89
    protected $cacheGenerated;
90
91
    /**
92
     * An array of entity IDs to exclude from the aggregate.
93
     *
94
     * @var string[]|null
95
     */
96
    protected $excluded;
97
98
    /**
99
     * An indexed array of protocols to filter the aggregate by. keys can be any of:
100
     *
101
     * - urn:oasis:names:tc:SAML:1.1:protocol
102
     * - urn:oasis:names:tc:SAML:2.0:protocol
103
     *
104
     * Values will be true if enabled, false otherwise.
105
     *
106
     * @var array|null
107
     */
108
    protected $protocols;
109
110
    /**
111
     * An array of roles to filter the aggregate by. Keys can be any of:
112
     *
113
     * - SAML2_XML_md_IDPSSODescriptor
114
     * - SAML2_XML_md_SPSSODescriptor
115
     * - SAML2_XML_md_AttributeAuthorityDescriptor
116
     *
117
     * Values will be true if enabled, false otherwise.
118
     *
119
     * @var array|null
120
     */
121
    protected $roles;
122
123
    /**
124
     * The key we should use to sign the metadata.
125
     *
126
     * @var string|null
127
     */
128
    protected $signKey;
129
130
    /**
131
     * The password for the private key.
132
     *
133
     * @var string|null
134
     */
135
    protected $signKeyPass;
136
137
    /**
138
     * The certificate of the key we sign the metadata with.
139
     *
140
     * @var string|null
141
     */
142
    protected $signCert;
143
144
    /**
145
     * The algorithm to use for metadata signing.
146
     *
147
     * @var string|null
148
     */
149
    protected $signAlg;
150
151
    /**
152
     * The CA certificate file that should be used to validate https-connections.
153
     *
154
     * @var string|null
155
     */
156
    protected $sslCAFile;
157
158
    /**
159
     * The cache ID for our generated metadata.
160
     *
161
     * @var string
162
     */
163
    protected $cacheId;
164
165
    /**
166
     * The cache tag for our generated metadata.
167
     *
168
     * This tag is used to make sure that a config change
169
     * invalidates our cached metadata.
170
     *
171
     * @var string
172
     */
173
    protected $cacheTag;
174
175
    /**
176
     * The registration information for our generated metadata.
177
     *
178
     * @var array
179
     */
180
    protected $regInfo;
181
182
183
    /**
184
     * Initialize this aggregator.
185
     *
186
     * @param string $id  The id of this aggregator.
187
     * @param \SimpleSAML\Configuration $config  The configuration for this aggregator.
188
     */
189
    protected function __construct($id, Configuration $config)
190
    {
191
        assert('is_string($id)');
192
193
        $this->id = $id;
194
        $this->logLoc = 'aggregator2:'.$this->id.': ';
195
196
        $this->cronTag = $config->getString('cron.tag', null);
197
198
        $this->cacheDirectory = $config->getString('cache.directory', null);
199
        if ($this->cacheDirectory !== null) {
200
            $this->cacheDirectory = System::resolvePath($this->cacheDirectory);
201
        }
202
203
        $this->cacheGenerated = $config->getInteger('cache.generated', null);
204
        if ($this->cacheGenerated !== null) {
205
            $this->cacheId = sha1($this->id);
206
            $this->cacheTag = sha1(serialize($config));
207
        }
208
209
        // configure entity IDs excluded by default
210
        $this->excludeEntities($config->getArrayize('exclude', null));
211
212
        // configure filters
213
        $this->setFilters($config->getArrayize('filter', null));
214
215
        $this->validLength = $config->getInteger('valid.length', 7*24*60*60);
216
217
        $globalConfig = Configuration::getInstance();
218
        $certDir = $globalConfig->getPathValue('certdir', 'cert/');
219
220
        $signKey = $config->getString('sign.privatekey', null);
221
        if ($signKey !== null) {
222
            $signKey = System::resolvePath($signKey, $certDir);
223
            $this->signKey = @file_get_contents($signKey);
224
            if ($this->signKey === null) {
225
                throw new Exception('Unable to load private key from '.var_export($signKey, true));
226
            }
227
        }
228
229
        $this->signKeyPass = $config->getString('sign.privatekey_pass', null);
230
231
        $signCert = $config->getString('sign.certificate', null);
232
        if ($signCert !== null) {
233
            $signCert = System::resolvePath($signCert, $certDir);
234
            $this->signCert = @file_get_contents($signCert);
235
            if ($this->signCert === null) {
236
                throw new Exception('Unable to load certificate file from '.var_export($signCert, true));
237
            }
238
        }
239
240
        $this->signAlg = $config->getString('sign.algorithm', XMLSecurityKey::RSA_SHA1);
241
        if (!in_array($this->signAlg, self::$SUPPORTED_SIGNATURE_ALGORITHMS)) {
242
            throw new Exception('Unsupported signature algorithm '.var_export($this->signAlg, true));
243
        }
244
245
        $this->sslCAFile = $config->getString('ssl.cafile', null);
246
247
        $this->regInfo = $config->getArray('RegistrationInfo', null);
248
249
        $this->initSources($config->getConfigList('sources'));
250
    }
251
252
253
    /**
254
     * Populate the sources array.
255
     *
256
     * This is called from the constructor, and can be overridden in subclasses.
257
     *
258
     * @param array $sources  The sources as an array of SimpleSAML_Configuration objects.
259
     */
260
    protected function initSources(array $sources)
261
    {
262
        foreach ($sources as $source) {
263
            $this->sources[] = new EntitySource($this, $source);
264
        }
265
    }
266
267
268
    /**
269
     * Return an instance of the aggregator with the given id.
270
     *
271
     * @param string $id  The id of the aggregator.
272
     */
273
    public static function getAggregator($id)
274
    {
275
        assert('is_string($id)');
276
277
        $config = Configuration::getConfig('module_aggregator2.php');
278
        return new Aggregator($id, $config->getConfigItem($id));
279
    }
280
281
282
    /**
283
     * Retrieve the ID of the aggregator.
284
     *
285
     * @return string  The ID of this aggregator.
286
     */
287
    public function getId()
288
    {
289
        return $this->id;
290
    }
291
292
293
    /**
294
     * Add an item to the cache.
295
     *
296
     * @param string $id  The identifier of this data.
297
     * @param string $data  The data.
298
     * @param int $expires  The timestamp the data expires.
299
     * @param string|null $tag  An extra tag that can be used to verify the validity of the cached data.
300
     */
301
    public function addCacheItem($id, $data, $expires, $tag = null)
302
    {
303
        assert('is_string($id)');
304
        assert('is_string($data)');
305
        assert('is_int($expires)');
306
        assert('is_null($tag) || is_string($tag)');
307
308
        $cacheFile = $this->cacheDirectory.'/'.$id;
309
        try {
310
            System::writeFile($cacheFile, $data);
311
        } catch (\Exception $e) {
312
            Logger::warning($this->logLoc.'Unable to write to cache file '.var_export($cacheFile, true));
313
            return;
314
        }
315
316
        $expireInfo = (string)$expires;
317
        if ($tag !== null) {
318
            $expireInfo .= ':'.$tag;
319
        }
320
321
        $expireFile = $cacheFile.'.expire';
322
        try {
323
            System::writeFile($expireFile, $expireInfo);
324
        } catch (\Exception $e) {
325
            Logger::warning($this->logLoc.'Unable to write expiration info to '.var_export($expireFile, true));
326
        }
327
    }
328
329
330
    /**
331
     * Check validity of cached data.
332
     *
333
     * @param string $id  The identifier of this data.
334
     * @param string $tag  The tag that was passed to addCacheItem.
335
     * @return bool  TRUE if the data is valid, FALSE if not.
336
     */
337
    public function isCacheValid($id, $tag = null)
338
    {
339
        assert('is_string($id)');
340
        assert('is_null($tag) || is_string($tag)');
341
342
        $cacheFile = $this->cacheDirectory.'/'.$id;
343
        if (!file_exists($cacheFile)) {
344
            return false;
345
        }
346
347
        $expireFile = $cacheFile.'.expire';
348
        if (!file_exists($expireFile)) {
349
            return false;
350
        }
351
352
        $expireData = @file_get_contents($expireFile);
353
        if ($expireData === false) {
354
            return false;
355
        }
356
357
        $expireData = explode(':', $expireData, 2);
358
359
        $expireTime = intval($expireData[0]);
360
        if ($expireTime <= time()) {
361
            return false;
362
        }
363
364
        if (count($expireData) === 1) {
365
            $expireTag = null;
366
        } else {
367
            $expireTag = $expireData[1];
368
        }
369
        if ($expireTag !== $tag) {
370
            return false;
371
        }
372
373
        return true;
374
    }
375
376
377
    /**
378
     * Get the cache item.
379
     *
380
     * @param string $id  The identifier of this data.
381
     * @param string $tag  The tag that was passed to addCacheItem.
382
     * @return string|null  The cache item, or NULL if it isn't cached or if it is expired.
383
     */
384
    public function getCacheItem($id, $tag = null)
385
    {
386
        assert('is_string($id)');
387
        assert('is_null($tag) || is_string($tag)');
388
389
        if (!$this->isCacheValid($id, $tag)) {
390
            return null;
391
        }
392
393
        $cacheFile = $this->cacheDirectory.'/'.$id;
394
        return @file_get_contents($cacheFile);
395
    }
396
397
398
    /**
399
     * Get the cache filename for the specific id.
400
     *
401
     * @param string $id  The identifier of the cached data.
402
     * @return string|null  The filename, or NULL if the cache file doesn't exist.
403
     */
404
    public function getCacheFile($id)
405
    {
406
        assert('is_string($id)');
407
408
        $cacheFile = $this->cacheDirectory.'/'.$id;
409
        if (!file_exists($cacheFile)) {
410
            return null;
411
        }
412
413
        return $cacheFile;
414
    }
415
416
417
    /**
418
     * Retrieve the SSL CA file path, if it is set.
419
     *
420
     * @return string|null  The SSL CA file path.
421
     */
422
    public function getCAFile()
423
    {
424
        return $this->sslCAFile;
425
    }
426
427
428
    /**
429
     * Sign the generated EntitiesDescriptor.
430
     */
431
    protected function addSignature(SignedElement $element)
432
    {
433
        if ($this->signKey === null) {
434
            return;
435
        }
436
437
        $privateKey = new XMLSecurityKey($this->signAlg, ['type' => 'private']);
438
        if ($this->signKeyPass !== null) {
439
            $privateKey->passphrase = $this->signKeyPass;
440
        }
441
        $privateKey->loadKey($this->signKey, false);
442
443
        $element->setSignatureKey($privateKey);
444
445
        if ($this->signCert !== null) {
446
            $element->setCertificates([$this->signCert]);
447
        }
448
    }
449
450
451
    /**
452
     * Recursively browse the children of an EntitiesDescriptor element looking for EntityDescriptor elements, and
453
     * return an array containing all of them.
454
     *
455
     * @param \SAML2\XML\md\EntitiesDescriptor $entity The source EntitiesDescriptor that holds the entities to extract.
456
     *
457
     * @return array An array containing all the EntityDescriptors found.
458
     */
459
    private static function extractEntityDescriptors($entity)
460
    {
461
        assert('$entity instanceof EntitiesDescriptor');
462
463
        if (!($entity instanceof EntitiesDescriptor)) {
0 ignored issues
show
introduced by
$entity is always a sub-type of SAML2\XML\md\EntitiesDescriptor.
Loading history...
464
            return [];
465
        }
466
467
        $results = [];
468
        foreach ($entity->children as $child) {
469
            if ($child instanceof EntityDescriptor) {
470
                $results[] = $child;
471
                continue;
472
            }
473
474
            $results = array_merge($results, self::extractEntityDescriptors($child));
475
        }
476
        return $results;
477
    }
478
479
480
    /**
481
     * Retrieve all entities as an EntitiesDescriptor.
482
     *
483
     * @return \SAML2\XML\md\EntitiesDescriptor  The entities.
484
     */
485
    protected function getEntitiesDescriptor()
486
    {
487
        $ret = new EntitiesDescriptor();
488
        $now = time();
489
490
        // add RegistrationInfo extension if enabled
491
        if ($this->regInfo !== null) {
492
            $ri = new RegistrationInfo();
493
            $ri->registrationInstant = $now;
494
            foreach ($this->regInfo as $riName => $riValues) {
495
                switch ($riName) {
496
                    case 'authority':
497
                        $ri->registrationAuthority = $riValues;
498
                        break;
499
                    case 'instant':
500
                        $ri->registrationInstant = Utils::xsDateTimeToTimestamp($riValues);
501
                        break;
502
                    case 'policies':
503
                        $ri->RegistrationPolicy = $riValues;
504
                        break;
505
                }
506
            }
507
            $ret->Extensions[] = $ri;
508
        }
509
510
        foreach ($this->sources as $source) {
511
            $m = $source->getMetadata();
512
            if ($m === NULL) {
513
                continue;
514
            }
515
            if ($m instanceof EntityDescriptor) {
516
                $ret->children[] = $m;
517
            } elseif ($m instanceof EntitiesDescriptor) {
518
                $ret->children = array_merge($ret->children, self::extractEntityDescriptors($m));
519
            }
520
        }
521
522
        $ret->children = array_unique($ret->children, SORT_REGULAR);
523
        $ret->validUntil = $now + $this->validLength;
524
525
        return $ret;
526
    }
527
528
529
    /**
530
     * Recursively traverse the children of an EntitiesDescriptor, removing those entities listed in the $entities
531
     * property. Returns the EntitiesDescriptor with the entities filtered out.
532
     *
533
     * @param \SAML2\XML\md\EntitiesDescriptor $descriptor The EntitiesDescriptor from where to exclude entities.
534
     *
535
     * @return \SAML2\XML\md\EntitiesDescriptor The EntitiesDescriptor with excluded entities filtered out.
536
     */
537
    protected function exclude(EntitiesDescriptor $descriptor)
538
    {
539
        if (empty($this->excluded)) {
540
            return $descriptor;
541
        }
542
543
        $filtered = [];
544
        foreach ($descriptor->children as $child) {
545
            if ($child instanceof EntityDescriptor) {
546
                if (in_array($child->entityID, $this->excluded)) {
547
                    continue;
548
                }
549
                $filtered[] = $child;
550
            }
551
552
            if ($child instanceof EntitiesDescriptor) {
553
                $filtered[] = $this->exclude($child);
554
            }
555
        }
556
557
        $descriptor->children = $filtered;
558
        return $descriptor;
559
    }
560
561
562
    /**
563
     * Recursively traverse the children of an EntitiesDescriptor, keeping only those entities with the roles listed in
564
     * the $roles property, and support for the protocols listed in the $protocols property. Returns the
565
     * EntitiesDescriptor containing only those entities.
566
     *
567
     * @param \SAML2\XML\md\EntitiesDescriptor $descriptor The EntitiesDescriptor to filter.
568
     *
569
     * @return SAML2_XML_md_EntitiesDescriptor The EntitiesDescriptor with only the entities filtered.
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Module\aggreg...L_md_EntitiesDescriptor 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...
570
     */
571
    protected function filter(EntitiesDescriptor $descriptor)
572
    {
573
        if ($this->roles === null || $this->protocols === null) {
574
            return $descriptor;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $descriptor returns the type SAML2\XML\md\EntitiesDescriptor which is incompatible with the documented return type SimpleSAML\Module\aggreg...L_md_EntitiesDescriptor.
Loading history...
575
        }
576
577
        $enabled_roles = array_keys($this->roles, true);
578
        $enabled_protos = array_keys($this->protocols, true);
579
580
        $filtered = [];
581
        foreach ($descriptor->children as $child) {
582
            if ($child instanceof EntityDescriptor) {
583
                foreach ($child->RoleDescriptor as $role) {
584
                    if (in_array(get_class($role), $enabled_roles)) {
585
                        // we found a role descriptor that is enabled by our filters, check protocols
586
                        if (array_intersect($enabled_protos, $role->protocolSupportEnumeration) !== []) {
587
                            // it supports some protocol we have enabled, add it
588
                            $filtered[] = $child;
589
                            break;
590
                        }
591
                    }
592
                }
593
594
            }
595
596
            if ($child instanceof EntitiesDescriptor) {
597
                $filtered[] = $this->filter($child);
598
            }
599
        }
600
601
        $descriptor->children = $filtered;
602
        return $descriptor;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $descriptor returns the type SAML2\XML\md\EntitiesDescriptor which is incompatible with the documented return type SimpleSAML\Module\aggreg...L_md_EntitiesDescriptor.
Loading history...
603
    }
604
605
606
    /**
607
     * Set this aggregator to exclude a set of entities from the resulting aggregate.
608
     *
609
     * @param array|null $entities The entity IDs of the entities to exclude.
610
     */
611
    public function excludeEntities($entities)
612
    {
613
        assert('is_array($entities) || is_null($entities)');
614
615
        if ($entities === null) {
616
            return;
617
        }
618
        $this->excluded = $entities;
619
        sort($this->excluded);
620
        $this->cacheId = sha1($this->cacheId.serialize($this->excluded));
621
    }
622
623
624
    /**
625
     * Set the internal filters according to one or more options:
626
     *
627
     * - 'saml2': all SAML2.0-capable entities.
628
     * - 'shib13': all SHIB1.3-capable entities.
629
     * - 'saml20-idp': all SAML2.0-capable identity providers.
630
     * - 'saml20-sp': all SAML2.0-capable service providers.
631
     * - 'saml20-aa': all SAML2.0-capable attribute authorities.
632
     * - 'shib13-idp': all SHIB1.3-capable identity providers.
633
     * - 'shib13-sp': all SHIB1.3-capable service providers.
634
     * - 'shib13-aa': all SHIB1.3-capable attribute authorities.
635
     *
636
     * @param array|null $set An array of the different roles and protocols to filter by.
637
     */
638
    public function setFilters($set)
639
    {
640
        assert('is_array($set) || is_null($set)');
641
642
        if ($set === null) {
643
            return;
644
        }
645
646
        // configure filters
647
        $this->protocols = [
648
            Constants::NS_SAMLP                    => true,
649
            'urn:oasis:names:tc:SAML:1.1:protocol' => true,
650
        ];
651
        $this->roles = [
652
            'SAML2_XML_md_IDPSSODescriptor'             => true,
653
            'SAML2_XML_md_SPSSODescriptor'              => true,
654
            'SAML2_XML_md_AttributeAuthorityDescriptor' => true,
655
        ];
656
657
        // now translate from the options we have, to specific protocols and roles
658
659
        // check SAML 2.0 protocol
660
        $options = ['saml2', 'saml20-idp', 'saml20-sp', 'saml20-aa'];
661
        $this->protocols[Constants::NS_SAMLP] = (array_intersect($set, $options) !== []);
662
663
        // check SHIB 1.3 protocol
664
        $options = ['shib13', 'shib13-idp', 'shib13-sp', 'shib13-aa'];
665
        $this->protocols['urn:oasis:names:tc:SAML:1.1:protocol'] = (array_intersect($set, $options) !== []);
666
667
        // check IdP
668
        $options = ['saml2', 'shib13', 'saml20-idp', 'shib13-idp'];
669
        $this->roles['SAML2_XML_md_IDPSSODescriptor'] = (array_intersect($set, $options) !== []);
670
671
        // check SP
672
        $options = ['saml2', 'shib13', 'saml20-sp', 'shib13-sp'];
673
        $this->roles['SAML2_XML_md_SPSSODescriptor'] = (array_intersect($set, $options) !== []);
674
675
        // check AA
676
        $options = ['saml2', 'shib13', 'saml20-aa', 'shib13-aa'];
677
        $this->roles['SAML2_XML_md_AttributeAuthorityDescriptor'] = (array_intersect($set, $options) !== []);
678
679
        $this->cacheId = sha1($this->cacheId.serialize($this->protocols).serialize($this->roles));
680
    }
681
682
683
    /**
684
     * Retrieve the complete, signed metadata as text.
685
     *
686
     * This function will write the new metadata to the cache file, but will not return
687
     * the cached metadata.
688
     *
689
     * @return string  The metadata, as text.
690
     */
691
    public function updateCachedMetadata()
692
    {
693
        $ed = $this->getEntitiesDescriptor();
694
        $ed = $this->exclude($ed);
695
        $ed = $this->filter($ed);
696
        $this->addSignature($ed);
697
698
        $xml = $ed->toXML();
699
        $xml = $xml->ownerDocument->saveXML($xml);
700
701
        if ($this->cacheGenerated !== null) {
702
            Logger::debug($this->logLoc.'Saving generated metadata to cache.');
703
            $this->addCacheItem($this->cacheId, $xml, time() + $this->cacheGenerated, $this->cacheTag);
704
        }
705
706
        return $xml;
707
    }
708
709
710
    /**
711
     * Retrieve the complete, signed metadata as text.
712
     *
713
     * @return string  The metadata, as text.
714
     */
715
    public function getMetadata()
716
    {
717
        if ($this->cacheGenerated !== null) {
718
            $xml = $this->getCacheItem($this->cacheId, $this->cacheTag);
719
            if ($xml !== null) {
720
                Logger::debug($this->logLoc.'Loaded generated metadata from cache.');
721
                return $xml;
722
            }
723
        }
724
725
        return $this->updateCachedMetadata();
726
    }
727
728
729
    /**
730
     * Update the cached copy of our metadata.
731
     */
732
    public function updateCache()
733
    {
734
        foreach ($this->sources as $source) {
735
            $source->updateCache();
736
        }
737
738
        $this->updateCachedMetadata();
739
    }
740
}
741