Passed
Pull Request — master (#5)
by Tim
01:52
created

Aggregator::getEntitiesDescriptor()   C

Complexity

Conditions 16
Paths 20

Size

Total Lines 64
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 16
eloc 46
c 1
b 0
f 0
nc 20
nop 0
dl 0
loc 64
rs 5.5666

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
namespace SimpleSAML\Module\aggregator2;
4
5
use \SimpleSAML\Error\Exception;
6
use \SimpleSAML\Configuration;
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
use \SAML2\XML\mdrpi\PublicationInfo;
17
18
use \RobRichards\XMLSecLibs\XMLSecurityKey;
19
20
/**
21
 * Class which implements a basic metadata aggregator.
22
 *
23
 * @package SimpleSAMLphp
24
 */
25
class Aggregator
26
{
27
    /**
28
     * The list of signature algorithms supported by the aggregator.
29
     *
30
     * @var array
31
     */
32
    public static $SUPPORTED_SIGNATURE_ALGORITHMS = [
33
        XMLSecurityKey::RSA_SHA1,
34
        XMLSecurityKey::RSA_SHA256,
35
        XMLSecurityKey::RSA_SHA384,
36
        XMLSecurityKey::RSA_SHA512,
37
    ];
38
39
    /**
40
     * The ID of this aggregator.
41
     *
42
     * @var string
43
     */
44
    protected $id;
45
46
    /**
47
     * Our log "location".
48
     *
49
     * @var string
50
     */
51
    protected $logLoc;
52
53
    /**
54
     * Which cron-tag this should be updated in.
55
     *
56
     * @var string|null
57
     */
58
    protected $cronTag;
59
60
    /**
61
     * Absolute path to a cache directory.
62
     *
63
     * @var string|null
64
     */
65
    protected $cacheDirectory;
66
67
    /**
68
     * The entity sources.
69
     *
70
     * Array of sspmod_aggregator2_EntitySource objects.
71
     *
72
     * @var array
73
     */
74
    protected $sources = [];
75
76
    /**
77
     * How long the generated metadata should be valid, as a number of seconds.
78
     *
79
     * This is used to set the validUntil attribute on the generated EntityDescriptor.
80
     *
81
     * @var int
82
     */
83
    protected $validLength;
84
85
    /**
86
     * Duration we should cache generated metadata.
87
     *
88
     * @var int|null
89
     */
90
    protected $cacheGenerated;
91
92
    /**
93
     * An array of entity IDs to exclude from the aggregate.
94
     *
95
     * @var string[]|null
96
     */
97
    protected $excluded;
98
99
    /**
100
     * An indexed array of protocols to filter the aggregate by. keys can be any of:
101
     *
102
     * - urn:oasis:names:tc:SAML:1.1:protocol
103
     * - urn:oasis:names:tc:SAML:2.0:protocol
104
     *
105
     * Values will be true if enabled, false otherwise.
106
     *
107
     * @var array|null
108
     */
109
    protected $protocols;
110
111
    /**
112
     * An array of roles to filter the aggregate by. Keys can be any of:
113
     *
114
     * - \SAML2\XML\md\IDPSSODescriptor
115
     * - \SAML2\XML\md\SPSSODescriptor
116
     * - \SAML2\XML\md\AttributeAuthorityDescriptor
117
     *
118
     * Values will be true if enabled, false otherwise.
119
     *
120
     * @var array|null
121
     */
122
    protected $roles;
123
124
    /**
125
     * The key we should use to sign the metadata.
126
     *
127
     * @var string|null
128
     */
129
    protected $signKey;
130
131
    /**
132
     * The password for the private key.
133
     *
134
     * @var string|null
135
     */
136
    protected $signKeyPass;
137
138
    /**
139
     * The certificate of the key we sign the metadata with.
140
     *
141
     * @var string|null
142
     */
143
    protected $signCert;
144
145
    /**
146
     * The algorithm to use for metadata signing.
147
     *
148
     * @var string|null
149
     */
150
    protected $signAlg;
151
152
    /**
153
     * The CA certificate file that should be used to validate https-connections.
154
     *
155
     * @var string|null
156
     */
157
    protected $sslCAFile;
158
159
    /**
160
     * The cache ID for our generated metadata.
161
     *
162
     * @var string
163
     */
164
    protected $cacheId = 'dummy';
165
166
    /**
167
     * The cache tag for our generated metadata.
168
     *
169
     * This tag is used to make sure that a config change
170
     * invalidates our cached metadata.
171
     *
172
     * @var string
173
     */
174
    protected $cacheTag = 'dummy';
175
176
    /**
177
     * The registration information for our generated metadata.
178
     *
179
     * @var array
180
     */
181
    protected $regInfo;
182
183
    /**
184
     * The publication information for our generated metadata.
185
     *
186
     * @var array
187
     */
188
    protected $pubInfo;
189
190
191
    /**
192
     * Initialize this aggregator.
193
     *
194
     * @param string $id  The id of this aggregator.
195
     * @param \SimpleSAML\Configuration $config  The configuration for this aggregator.
196
     */
197
    protected function __construct($id, Configuration $config)
198
    {
199
        assert('is_string($id)');
200
201
        $this->id = $id;
202
        $this->logLoc = 'aggregator2:'.$this->id.': ';
203
204
        $this->cronTag = $config->getString('cron.tag', null);
205
206
        $this->cacheDirectory = $config->getString('cache.directory', null);
207
        if ($this->cacheDirectory !== null) {
208
            $this->cacheDirectory = System::resolvePath($this->cacheDirectory);
209
        }
210
211
        $this->cacheGenerated = $config->getInteger('cache.generated', null);
212
        if ($this->cacheGenerated !== null) {
213
            $this->cacheId = sha1($this->id);
214
            $this->cacheTag = sha1(serialize($config));
215
        }
216
217
        // configure entity IDs excluded by default
218
        $this->excludeEntities($config->getArrayize('exclude', null));
219
220
        // configure filters
221
        $this->setFilters($config->getArrayize('filter', null));
222
223
        $this->validLength = $config->getInteger('valid.length', 7*24*60*60);
224
225
        $globalConfig = Configuration::getInstance();
226
        $certDir = $globalConfig->getPathValue('certdir', 'cert/');
227
228
        $signKey = $config->getString('sign.privatekey', null);
229
        if ($signKey !== null) {
230
            $signKey = System::resolvePath($signKey, $certDir);
231
            $sk = @file_get_contents($signKey);
232
            if ($sk === false) {
233
                throw new Exception('Unable to load private key from '.var_export($signKey, true));
234
            }
235
            $this->signKey = $sk;
236
        }
237
238
        $this->signKeyPass = $config->getString('sign.privatekey_pass', null);
239
240
        $signCert = $config->getString('sign.certificate', null);
241
        if ($signCert !== null) {
242
            $signCert = System::resolvePath($signCert, $certDir);
243
            $sc = @file_get_contents($signCert);
244
            if ($sc === false) {
245
                throw new Exception('Unable to load certificate file from '.var_export($signCert, true));
246
            }
247
            $this->signCert = $sc;
248
        }
249
250
        $this->signAlg = $config->getString('sign.algorithm', XMLSecurityKey::RSA_SHA1);
251
        if (!in_array($this->signAlg, self::$SUPPORTED_SIGNATURE_ALGORITHMS)) {
252
            throw new Exception('Unsupported signature algorithm '.var_export($this->signAlg, true));
253
        }
254
255
        $this->sslCAFile = $config->getString('ssl.cafile', null);
256
257
        $this->regInfo = $config->getArray('RegistrationInfo', []);
258
        $this->pubInfo = $config->getArray('PublicationInfo', []);
259
260
        $this->initSources($config->getConfigList('sources'));
0 ignored issues
show
Bug introduced by
It seems like $config->getConfigList('sources') can also be of type string; however, parameter $sources of SimpleSAML\Module\aggreg...gregator::initSources() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

260
        $this->initSources(/** @scrutinizer ignore-type */ $config->getConfigList('sources'));
Loading history...
261
    }
262
263
264
    /**
265
     * Populate the sources array.
266
     *
267
     * This is called from the constructor, and can be overridden in subclasses.
268
     *
269
     * @param array $sources  The sources as an array of \SimpleSAML\Configuration objects.
270
     * @return void
271
     */
272
    protected function initSources(array $sources)
273
    {
274
        foreach ($sources as $source) {
275
            $this->sources[] = new EntitySource($this, $source);
276
        }
277
    }
278
279
280
    /**
281
     * Return an instance of the aggregator with the given id.
282
     *
283
     * @param string $id  The id of the aggregator.
284
     * @return Aggregator
285
     */
286
    public static function getAggregator($id)
287
    {
288
        assert('is_string($id)');
289
290
        $config = Configuration::getConfig('module_aggregator2.php');
291
        return new Aggregator($id, $config->getConfigItem($id));
0 ignored issues
show
Bug introduced by
It seems like $config->getConfigItem($id) can also be of type string; however, parameter $config of SimpleSAML\Module\aggreg...gregator::__construct() does only seem to accept SimpleSAML\Configuration, maybe add an additional type check? ( Ignorable by Annotation )

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

291
        return new Aggregator($id, /** @scrutinizer ignore-type */ $config->getConfigItem($id));
Loading history...
292
    }
293
294
295
    /**
296
     * Retrieve the ID of the aggregator.
297
     *
298
     * @return string  The ID of this aggregator.
299
     */
300
    public function getId()
301
    {
302
        return $this->id;
303
    }
304
305
306
    /**
307
     * Add an item to the cache.
308
     *
309
     * @param string $id  The identifier of this data.
310
     * @param string $data  The data.
311
     * @param int $expires  The timestamp the data expires.
312
     * @param string|null $tag  An extra tag that can be used to verify the validity of the cached data.
313
     * @return void
314
     */
315
    public function addCacheItem($id, $data, $expires, $tag = null)
316
    {
317
        assert('is_string($id)');
318
        assert('is_string($data)');
319
        assert('is_int($expires)');
320
        assert('is_null($tag) || is_string($tag)');
321
322
        $cacheFile = strval($this->cacheDirectory).'/'.$id;
323
        try {
324
            System::writeFile($cacheFile, $data);
325
        } catch (\Exception $e) {
326
            Logger::warning($this->logLoc.'Unable to write to cache file '.var_export($cacheFile, true));
327
            return;
328
        }
329
330
        $expireInfo = (string)$expires;
331
        if ($tag !== null) {
332
            $expireInfo .= ':'.$tag;
333
        }
334
335
        $expireFile = $cacheFile.'.expire';
336
        try {
337
            System::writeFile($expireFile, $expireInfo);
338
        } catch (\Exception $e) {
339
            Logger::warning($this->logLoc.'Unable to write expiration info to '.var_export($expireFile, true));
340
        }
341
    }
342
343
344
    /**
345
     * Check validity of cached data.
346
     *
347
     * @param string $id  The identifier of this data.
348
     * @param string $tag  The tag that was passed to addCacheItem.
349
     * @return bool  TRUE if the data is valid, FALSE if not.
350
     */
351
    public function isCacheValid($id, $tag = null)
352
    {
353
        assert('is_string($id)');
354
        assert('is_null($tag) || is_string($tag)');
355
356
        $cacheFile = strval($this->cacheDirectory).'/'.$id;
357
        if (!file_exists($cacheFile)) {
358
            return false;
359
        }
360
361
        $expireFile = $cacheFile.'.expire';
362
        if (!file_exists($expireFile)) {
363
            return false;
364
        }
365
366
        $expireData = @file_get_contents($expireFile);
367
        if ($expireData === false) {
368
            return false;
369
        }
370
371
        $expireData = explode(':', $expireData, 2);
372
373
        $expireTime = intval($expireData[0]);
374
        if ($expireTime <= time()) {
375
            return false;
376
        }
377
378
        if (count($expireData) === 1) {
379
            $expireTag = null;
380
        } else {
381
            $expireTag = $expireData[1];
382
        }
383
        if ($expireTag !== $tag) {
384
            return false;
385
        }
386
387
        return true;
388
    }
389
390
391
    /**
392
     * Get the cache item.
393
     *
394
     * @param string $id  The identifier of this data.
395
     * @param string $tag  The tag that was passed to addCacheItem.
396
     * @return string|null  The cache item, or NULL if it isn't cached or if it is expired.
397
     */
398
    public function getCacheItem($id, $tag = null)
399
    {
400
        assert('is_string($id)');
401
        assert('is_null($tag) || is_string($tag)');
402
403
        if (!$this->isCacheValid($id, $tag)) {
404
            return null;
405
        }
406
407
        $cacheFile = strval($this->cacheDirectory).'/'.$id;
408
        return @file_get_contents($cacheFile);
409
    }
410
411
412
    /**
413
     * Get the cache filename for the specific id.
414
     *
415
     * @param string $id  The identifier of the cached data.
416
     * @return string|null  The filename, or NULL if the cache file doesn't exist.
417
     */
418
    public function getCacheFile($id)
419
    {
420
        assert('is_string($id)');
421
422
        $cacheFile = strval($this->cacheDirectory).'/'.$id;
423
        if (!file_exists($cacheFile)) {
424
            return null;
425
        }
426
427
        return $cacheFile;
428
    }
429
430
431
    /**
432
     * Retrieve the SSL CA file path, if it is set.
433
     *
434
     * @return string|null  The SSL CA file path.
435
     */
436
    public function getCAFile()
437
    {
438
        return $this->sslCAFile;
439
    }
440
441
442
    /**
443
     * Sign the generated EntitiesDescriptor.
444
     * @return void
445
     */
446
    protected function addSignature(SignedElement $element)
447
    {
448
        if ($this->signKey === null) {
449
            return;
450
        }
451
452
        /** @var string $this->signAlg */
453
        $privateKey = new XMLSecurityKey($this->signAlg, ['type' => 'private']);
454
        if ($this->signKeyPass !== null) {
455
            $privateKey->passphrase = $this->signKeyPass;
456
        }
457
        $privateKey->loadKey($this->signKey, false);
458
459
        $element->setSignatureKey($privateKey);
460
461
        if ($this->signCert !== null) {
462
            $element->setCertificates([$this->signCert]);
463
        }
464
    }
465
466
467
    /**
468
     * Recursively browse the children of an EntitiesDescriptor element looking for EntityDescriptor elements, and
469
     * return an array containing all of them.
470
     *
471
     * @param \SAML2\XML\md\EntitiesDescriptor $entity The source EntitiesDescriptor that holds the entities to extract.
472
     *
473
     * @return array An array containing all the EntityDescriptors found.
474
     */
475
    private static function extractEntityDescriptors(EntitiesDescriptor $entity)
476
    {
477
        $results = [];
478
        foreach ($entity->children as $child) {
479
            if ($child instanceof EntityDescriptor) {
480
                $results[] = $child;
481
                continue;
482
            }
483
484
            $results = array_merge($results, self::extractEntityDescriptors($child));
485
        }
486
        return $results;
487
    }
488
489
490
    /**
491
     * Retrieve all entities as an EntitiesDescriptor.
492
     *
493
     * @return \SAML2\XML\md\EntitiesDescriptor  The entities.
494
     */
495
    protected function getEntitiesDescriptor()
496
    {
497
        $ret = new EntitiesDescriptor();
498
        $now = time();
499
500
        // add RegistrationInfo extension if enabled
501
        if (!empty($this->regInfo)) {
502
            $ri = new RegistrationInfo();
503
            $ri->setRegistrationInstant($now);
504
            foreach ($this->regInfo as $riName => $riValues) {
505
                switch ($riName) {
506
                    case 'authority':
507
                        $ri->setRegistrationAuthority($riValues);
508
                        break;
509
                    case 'instant':
510
                        $ri->setRegistrationInstant(Utils::xsDateTimeToTimestamp($riValues));
511
                        break;
512
                    case 'policies':
513
                        $ri->setRegistrationPolicy($riValues);
514
                        break;
515
                }
516
            }
517
            $ret->Extensions[] = $ri;
518
        }
519
520
        // add PublicationInfo extension if enabled
521
        if (!empty($this->pubInfo)) {
522
            $pi = new PublicationInfo();
523
            $pi->setCreationInstant($now);
524
            foreach ($this->pubInfo as $piName => $piValues) {
525
                switch ($piName) {
526
                    case 'publisher':
527
                        $pi->setPublisher($piValues);
528
                        break;
529
                    case 'publicationId':
530
                        $pi->setPublicationId($piValues);
531
                        break;
532
                    case 'instant':
533
                        $pi->setCreationInstant(Utils::xsDateTimeToTimestamp($piValues));
534
                        break;
535
                    case 'policies':
536
                        $pi->setUsagePolicy($piValues);
537
                        break;
538
                }
539
            }
540
            $ret->Extensions[] = $pi;
541
        }
542
543
        foreach ($this->sources as $source) {
544
            $m = $source->getMetadata();
545
            if ($m === null) {
546
                continue;
547
            }
548
            if ($m instanceof EntityDescriptor) {
549
                $ret->children[] = $m;
550
            } elseif ($m instanceof EntitiesDescriptor) {
551
                $ret->children = array_merge($ret->children, self::extractEntityDescriptors($m));
552
            }
553
        }
554
555
        $ret->children = array_unique($ret->children, SORT_REGULAR);
556
        $ret->validUntil = $now + $this->validLength;
557
558
        return $ret;
559
    }
560
561
562
    /**
563
     * Recursively traverse the children of an EntitiesDescriptor, removing those entities listed in the $entities
564
     * property. Returns the EntitiesDescriptor with the entities filtered out.
565
     *
566
     * @param \SAML2\XML\md\EntitiesDescriptor $descriptor The EntitiesDescriptor from where to exclude entities.
567
     *
568
     * @return \SAML2\XML\md\EntitiesDescriptor The EntitiesDescriptor with excluded entities filtered out.
569
     */
570
    protected function exclude(EntitiesDescriptor $descriptor)
571
    {
572
        if (empty($this->excluded)) {
573
            return $descriptor;
574
        }
575
576
        $filtered = [];
577
        foreach ($descriptor->children as $child) {
578
            if ($child instanceof EntityDescriptor) {
579
                if (in_array($child->entityID, $this->excluded)) {
580
                    continue;
581
                }
582
                $filtered[] = $child;
583
            }
584
585
            if ($child instanceof EntitiesDescriptor) {
586
                $filtered[] = $this->exclude($child);
587
            }
588
        }
589
590
        $descriptor->children = $filtered;
591
        return $descriptor;
592
    }
593
594
595
    /**
596
     * Recursively traverse the children of an EntitiesDescriptor, keeping only those entities with the roles listed in
597
     * the $roles property, and support for the protocols listed in the $protocols property. Returns the
598
     * EntitiesDescriptor containing only those entities.
599
     *
600
     * @param \SAML2\XML\md\EntitiesDescriptor $descriptor The EntitiesDescriptor to filter.
601
     *
602
     * @return \SAML2\XML\md\EntitiesDescriptor The EntitiesDescriptor with only the entities filtered.
603
     */
604
    protected function filter(EntitiesDescriptor $descriptor)
605
    {
606
        if ($this->roles === null || $this->protocols === null) {
607
            return $descriptor;
608
        }
609
610
        $enabled_roles = array_keys($this->roles, true);
611
        $enabled_protos = array_keys($this->protocols, true);
612
613
        $filtered = [];
614
        foreach ($descriptor->children as $child) {
615
            if ($child instanceof EntityDescriptor) {
616
                foreach ($child->RoleDescriptor as $role) {
617
                    if (in_array(get_class($role), $enabled_roles)) {
618
                        // we found a role descriptor that is enabled by our filters, check protocols
619
                        if (array_intersect($enabled_protos, $role->protocolSupportEnumeration) !== []) {
620
                            // it supports some protocol we have enabled, add it
621
                            $filtered[] = $child;
622
                            break;
623
                        }
624
                    }
625
                }
626
627
            }
628
629
            if ($child instanceof EntitiesDescriptor) {
630
                $filtered[] = $this->filter($child);
631
            }
632
        }
633
634
        $descriptor->children = $filtered;
635
        return $descriptor;
636
    }
637
638
639
    /**
640
     * Set this aggregator to exclude a set of entities from the resulting aggregate.
641
     *
642
     * @param array|null $entities The entity IDs of the entities to exclude.
643
     * @return void
644
     */
645
    public function excludeEntities($entities)
646
    {
647
        assert('is_array($entities) || is_null($entities)');
648
649
        if ($entities === null) {
650
            return;
651
        }
652
        $this->excluded = $entities;
653
        sort($this->excluded);
654
        $this->cacheId = sha1($this->cacheId.serialize($this->excluded));
655
    }
656
657
658
    /**
659
     * Set the internal filters according to one or more options:
660
     *
661
     * - 'saml2': all SAML2.0-capable entities.
662
     * - 'shib13': all SHIB1.3-capable entities.
663
     * - 'saml20-idp': all SAML2.0-capable identity providers.
664
     * - 'saml20-sp': all SAML2.0-capable service providers.
665
     * - 'saml20-aa': all SAML2.0-capable attribute authorities.
666
     * - 'shib13-idp': all SHIB1.3-capable identity providers.
667
     * - 'shib13-sp': all SHIB1.3-capable service providers.
668
     * - 'shib13-aa': all SHIB1.3-capable attribute authorities.
669
     *
670
     * @param array|null $set An array of the different roles and protocols to filter by.
671
     * @return void
672
     */
673
    public function setFilters($set)
674
    {
675
        assert('is_array($set) || is_null($set)');
676
677
        if ($set === null) {
678
            return;
679
        }
680
681
        // configure filters
682
        $this->protocols = [
683
            Constants::NS_SAMLP                    => true,
684
            'urn:oasis:names:tc:SAML:1.1:protocol' => true,
685
        ];
686
        $this->roles = [
687
            'SAML2_XML_md_IDPSSODescriptor'             => true,
688
            'SAML2_XML_md_SPSSODescriptor'              => true,
689
            'SAML2_XML_md_AttributeAuthorityDescriptor' => true,
690
        ];
691
692
        // now translate from the options we have, to specific protocols and roles
693
694
        // check SAML 2.0 protocol
695
        $options = ['saml2', 'saml20-idp', 'saml20-sp', 'saml20-aa'];
696
        $this->protocols[Constants::NS_SAMLP] = (array_intersect($set, $options) !== []);
697
698
        // check SHIB 1.3 protocol
699
        $options = ['shib13', 'shib13-idp', 'shib13-sp', 'shib13-aa'];
700
        $this->protocols['urn:oasis:names:tc:SAML:1.1:protocol'] = (array_intersect($set, $options) !== []);
701
702
        // check IdP
703
        $options = ['saml2', 'shib13', 'saml20-idp', 'shib13-idp'];
704
        $this->roles['SAML2_XML_md_IDPSSODescriptor'] = (array_intersect($set, $options) !== []);
705
706
        // check SP
707
        $options = ['saml2', 'shib13', 'saml20-sp', 'shib13-sp'];
708
        $this->roles['SAML2_XML_md_SPSSODescriptor'] = (array_intersect($set, $options) !== []);
709
710
        // check AA
711
        $options = ['saml2', 'shib13', 'saml20-aa', 'shib13-aa'];
712
        $this->roles['SAML2_XML_md_AttributeAuthorityDescriptor'] = (array_intersect($set, $options) !== []);
713
714
        $this->cacheId = sha1($this->cacheId.serialize($this->protocols).serialize($this->roles));
715
    }
716
717
718
    /**
719
     * Retrieve the complete, signed metadata as text.
720
     *
721
     * This function will write the new metadata to the cache file, but will not return
722
     * the cached metadata.
723
     *
724
     * @return string  The metadata, as text.
725
     */
726
    public function updateCachedMetadata()
727
    {
728
        $ed = $this->getEntitiesDescriptor();
729
        $ed = $this->exclude($ed);
730
        $ed = $this->filter($ed);
731
        $this->addSignature($ed);
732
733
        $xml = $ed->toXML();
734
        $xml = $xml->ownerDocument->saveXML($xml);
735
736
        if ($this->cacheGenerated !== null) {
737
            Logger::debug($this->logLoc.'Saving generated metadata to cache.');
738
            $this->addCacheItem($this->cacheId, $xml, time() + $this->cacheGenerated, $this->cacheTag);
739
        }
740
741
        return $xml;
742
    }
743
744
745
    /**
746
     * Retrieve the complete, signed metadata as text.
747
     *
748
     * @return string  The metadata, as text.
749
     */
750
    public function getMetadata()
751
    {
752
        if ($this->cacheGenerated !== null) {
753
            $xml = $this->getCacheItem($this->cacheId, $this->cacheTag);
754
            if ($xml !== null) {
755
                Logger::debug($this->logLoc.'Loaded generated metadata from cache.');
756
                return $xml;
757
            }
758
        }
759
760
        return $this->updateCachedMetadata();
761
    }
762
763
764
    /**
765
     * Update the cached copy of our metadata.
766
     * @return void
767
     */
768
    public function updateCache()
769
    {
770
        foreach ($this->sources as $source) {
771
            $source->updateCache();
772
        }
773
774
        $this->updateCachedMetadata();
775
    }
776
}
777