Issues (8)

src/Aggregator.php (8 issues)

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

273
        $this->initSources(/** @scrutinizer ignore-type */ $config->getOptionalArray('sources', []));
Loading history...
274
    }
275
276
277
    /**
278
     * Populate the sources array.
279
     *
280
     * This is called from the constructor, and can be overridden in subclasses.
281
     *
282
     * @param array<mixed> $sources  The sources as an array of \SimpleSAML\Configuration objects.
283
     */
284
    protected function initSources(array $sources): void
285
    {
286
        foreach ($sources as $source) {
287
            $this->sources[] = new EntitySource($this, Configuration::loadFromArray($source));
288
        }
289
    }
290
291
292
    /**
293
     * Return an instance of the aggregator with the given id.
294
     *
295
     * @param string $id  The id of the aggregator.
296
     * @return Aggregator
297
     */
298
    public static function getAggregator(string $id): Aggregator
299
    {
300
        $config = Configuration::getConfig('module_aggregator2.php');
301
        /** @psalm-suppress PossiblyNullArgument */
302
        return new Aggregator($id, $config->getOptionalConfigItem($id, []));
0 ignored issues
show
It seems like $config->getOptionalConfigItem($id, array()) can also be of type null; 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

302
        return new Aggregator($id, /** @scrutinizer ignore-type */ $config->getOptionalConfigItem($id, []));
Loading history...
303
    }
304
305
306
    /**
307
     * Retrieve the ID of the aggregator.
308
     *
309
     * @return string  The ID of this aggregator.
310
     */
311
    public function getId(): string
312
    {
313
        return $this->id;
314
    }
315
316
317
    /**
318
     * Add an item to the cache.
319
     *
320
     * @param string $id  The identifier of this data.
321
     * @param string $data  The data.
322
     * @param \DateTimeImmutable $expires  The timestamp the data expires.
323
     * @param string|null $tag  An extra tag that can be used to verify the validity of the cached data.
324
     */
325
    public function addCacheItem(string $id, string $data, DateTimeImmutable $expires, string $tag = null): void
326
    {
327
        $sysUtils = new Utils\System();
328
        $cacheFile = strval($this->cacheDirectory) . '/' . $id;
329
        try {
330
            $sysUtils->writeFile($cacheFile, $data);
331
        } catch (Exception $e) {
332
            Logger::warning($this->logLoc . 'Unable to write to cache file ' . var_export($cacheFile, true));
333
            return;
334
        }
335
336
        $expireInfo = strval($expires->getTimestamp());
337
        if ($tag !== null) {
338
            $expireInfo .= ':' . $tag;
339
        }
340
341
        $expireFile = $cacheFile . '.expire';
342
        try {
343
            $sysUtils->writeFile($expireFile, $expireInfo);
344
        } catch (Exception $e) {
345
            Logger::warning($this->logLoc . 'Unable to write expiration info to ' . var_export($expireFile, true));
346
        }
347
    }
348
349
350
    /**
351
     * Check validity of cached data.
352
     *
353
     * @param string $id  The identifier of this data.
354
     * @param string|null $tag  The tag that was passed to addCacheItem.
355
     * @return bool  TRUE if the data is valid, FALSE if not.
356
     */
357
    public function isCacheValid(string $id, string $tag = null): bool
358
    {
359
        $cacheFile = strval($this->cacheDirectory) . '/' . $id;
360
        if (!file_exists($cacheFile)) {
361
            return false;
362
        }
363
364
        $expireFile = $cacheFile . '.expire';
365
        if (!file_exists($expireFile)) {
366
            return false;
367
        }
368
369
        $expireData = @file_get_contents($expireFile);
370
        if ($expireData === false) {
371
            return false;
372
        }
373
374
        $expireData = explode(':', $expireData, 2);
375
376
        $expireTime = intval($expireData[0]);
377
        if ($expireTime <= time()) {
378
            return false;
379
        }
380
381
        if (count($expireData) === 1) {
382
            $expireTag = null;
383
        } else {
384
            $expireTag = $expireData[1];
385
        }
386
        if ($expireTag !== $tag) {
387
            return false;
388
        }
389
390
        return true;
391
    }
392
393
394
    /**
395
     * Get the cache item.
396
     *
397
     * @param string $id  The identifier of this data.
398
     * @param string|null $tag  The tag that was passed to addCacheItem.
399
     * @return string|null  The cache item, or NULL if it isn't cached or if it is expired.
400
     */
401
    public function getCacheItem(string $id, string $tag = null): ?string
402
    {
403
        if (!$this->isCacheValid($id, $tag)) {
404
            return null;
405
        }
406
407
        $cacheFile = strval($this->cacheDirectory) . '/' . $id;
408
        return @file_get_contents($cacheFile);
0 ignored issues
show
Bug Best Practice introduced by
The expression return @file_get_contents($cacheFile) could return the type false which is incompatible with the type-hinted return null|string. Consider adding an additional type-check to rule them out.
Loading history...
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(string $id): ?string
419
    {
420
        $cacheFile = strval($this->cacheDirectory) . '/' . $id;
421
        if (!file_exists($cacheFile)) {
422
            return null;
423
        }
424
425
        return $cacheFile;
426
    }
427
428
429
    /**
430
     * Retrieve the SSL CA file path, if it is set.
431
     *
432
     * @return string|null  The SSL CA file path.
433
     */
434
    public function getCAFile(): ?string
435
    {
436
        return $this->sslCAFile;
437
    }
438
439
440
    /**
441
     * Sign the generated EntitiesDescriptor.
442
     */
443
    protected function addSignature(SignableElementInterface $element): void
444
    {
445
        if ($this->signKey === null) {
446
            return;
447
        }
448
449
        $keyInfo = null;
450
        if ($this->signCert !== null) {
451
            $keyInfo = new KeyInfo(
452
                [
453
                    new X509Data(
454
                        [
455
                            new X509Certificate($this->signCert->getMaterial()),
0 ignored issues
show
The method getMaterial() does not exist on SimpleSAML\XMLSecurity\CryptoEncoding\PEM. ( Ignorable by Annotation )

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

455
                            new X509Certificate($this->signCert->/** @scrutinizer ignore-call */ getMaterial()),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
456
                        ],
457
                    ),
458
                ],
459
            );
460
        }
461
462
        /** @var string $this->signAlg */
463
        $key = PrivateKey::fromFile($this->signKey, $this->signKeyPass);
0 ignored issues
show
It seems like $this->signKeyPass can also be of type null; however, parameter $passphrase of SimpleSAML\XMLSecurity\Key\PrivateKey::fromFile() does only seem to accept string, 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

463
        $key = PrivateKey::fromFile($this->signKey, /** @scrutinizer ignore-type */ $this->signKeyPass);
Loading history...
464
        $signer = (new SignatureAlgorithmFactory())->getAlgorithm(
465
            $this->signAlg,
0 ignored issues
show
It seems like $this->signAlg can also be of type null; however, parameter $algId of SimpleSAML\XMLSecurity\A...Factory::getAlgorithm() does only seem to accept string, 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

465
            /** @scrutinizer ignore-type */ $this->signAlg,
Loading history...
466
            $key
467
        );
468
469
        $element->sign($signer, C::C14N_EXCLUSIVE_WITHOUT_COMMENTS, $keyInfo);
470
    }
471
472
473
    /**
474
     * Recursively browse the children of an EntitiesDescriptor element looking for EntityDescriptor elements, and
475
     * return an array containing all of them.
476
     *
477
     * @param \SAML2\XML\md\EntitiesDescriptor $entity The source EntitiesDescriptor that holds the entities to extract.
478
     *
479
     * @return array An array containing all the EntityDescriptors found.
480
     */
481
    private static function extractEntityDescriptors(EntitiesDescriptor $entity): array
482
    {
483
        $results = [];
484
        $descriptors = array_merge($entity->getEntityDescriptors(), $entity->getEntitiesDescriptors());
485
        foreach ($descriptors as $child) {
486
            if ($child instanceof EntityDescriptor) {
487
                $results[] = $child;
488
                continue;
489
            }
490
491
            $results = array_merge($results, self::extractEntityDescriptors($child));
492
        }
493
        return $results;
494
    }
495
496
497
    /**
498
     * Retrieve all entities as an EntitiesDescriptor.
499
     *
500
     * @return \SimpleSAML\SAML2\XML\md\EntitiesDescriptor  The entities.
501
     */
502
    protected function getEntitiesDescriptor(): EntitiesDescriptor
503
    {
504
        $extensions = [];
505
506
        // add RegistrationInfo extension if enabled
507
        if (!empty($this->regInfo)) {
508
            $extensions[] = RegistrationInfo::fromArray($this->regInfo);
509
        }
510
511
        // add PublicationInfo extension if enabled
512
        if (!empty($this->pubInfo)) {
513
            $extensions[] = PublicationInfo::fromArray($this->pubInfo);
514
        }
515
516
        $children = [];
517
        foreach ($this->sources as $source) {
518
            $m = $source->getMetadata();
519
            if ($m === null) {
520
                continue;
521
            }
522
523
            if ($m instanceof EntityDescriptor) {
524
                $children[] = $m;
525
            } elseif ($m instanceof EntitiesDescriptor) {
526
                $children = array_merge($children, self::extractEntityDescriptors($m));
527
            }
528
        }
529
        $children = array_unique($children, SORT_REGULAR);
530
531
        $now = new DateTimeImmutable('@' . strval(time() + $this->validLength));
532
        $now = $now->setTimeZone(new DateTimeZone('Z'));
533
534
        $ret = new EntitiesDescriptor(
535
            entityDescriptors: $children,
536
            validUntil: $now,
537
            extensions: empty($extensions) ? null : new Extensions($extensions),
538
            Name: $this->name,
539
        );
540
541
        return $ret;
542
    }
543
544
545
    /**
546
     * Recursively traverse the children of an EntitiesDescriptor, removing those entities listed in the $entities
547
     * property. Returns the EntitiesDescriptor with the entities filtered out.
548
     *
549
     * @param \SimpleSAML\SAML2\XML\md\EntitiesDescriptor $descriptor
550
     *   The EntitiesDescriptor from where to exclude entities.
551
     *
552
     * @return \SimpleSAML\SAML2\XML\md\EntitiesDescriptor The EntitiesDescriptor with excluded entities filtered out.
553
     */
554
    protected function exclude(EntitiesDescriptor $descriptor): EntitiesDescriptor
555
    {
556
        if (empty($this->excluded)) {
557
            return $descriptor;
558
        }
559
560
        $descriptors = array_merge($descriptor->getEntityDescriptors(), $descriptor->getEntitiesDescriptors());
561
        $filtered = [];
562
        foreach ($descriptors as $child) {
563
            if ($child instanceof EntityDescriptor) {
564
                if (in_array($child->getEntityID(), $this->excluded)) {
565
                    continue;
566
                }
567
                $filtered[] = $child;
568
            }
569
570
            if ($child instanceof EntitiesDescriptor) {
571
                $filtered[] = $this->exclude($child);
572
            }
573
        }
574
575
576
        return new EntitiesDescriptor($filtered);
577
    }
578
579
580
    /**
581
     * Recursively traverse the children of an EntitiesDescriptor, keeping only those entities with the roles listed in
582
     * the $roles property, and support for the protocols listed in the $protocols property. Returns the
583
     * EntitiesDescriptor containing only those entities.
584
     *
585
     * @param \SimpleSAML\SAML2\XML\md\EntitiesDescriptor $descriptor The EntitiesDescriptor to filter.
586
     *
587
     * @return \SimpleSAML\SAML2\XML\md\EntitiesDescriptor The EntitiesDescriptor with only the entities filtered.
588
     */
589
    protected function filter(EntitiesDescriptor $descriptor): EntitiesDescriptor
590
    {
591
        if (empty($this->roles) || empty($this->protocols)) {
592
            return $descriptor;
593
        }
594
595
        $enabled_roles = array_keys($this->roles, true);
596
        $enabled_protos = array_keys($this->protocols, true);
597
598
        $descriptors = array_merge($descriptor->getEntityDescriptors(), $descriptor->getEntitiesDescriptors());
599
        $filtered = [];
600
        foreach ($descriptors as $child) {
601
            if ($child instanceof EntityDescriptor) {
602
                foreach ($child->getRoleDescriptor() as $role) {
603
                    if (in_array(get_class($role), $enabled_roles)) {
604
                        // we found a role descriptor that is enabled by our filters, check protocols
605
                        if (array_intersect($enabled_protos, $role->getProtocolSupportEnumeration()) !== []) {
606
                            // it supports some protocol we have enabled, add it
607
                            $filtered[] = $child;
608
                            break;
609
                        }
610
                    }
611
                }
612
            }
613
614
            if ($child instanceof EntitiesDescriptor) {
615
                $filtered[] = $this->filter($child);
616
            }
617
        }
618
619
        return new EntitiesDescriptor($filtered);
620
    }
621
622
623
    /**
624
     * Set this aggregator to exclude a set of entities from the resulting aggregate.
625
     *
626
     * @param array $entities The entity IDs of the entities to exclude.
627
     */
628
    public function excludeEntities(array $entities): void
629
    {
630
        if (empty($entities)) {
631
            return;
632
        }
633
        $this->excluded = $entities;
634
        sort($this->excluded);
635
        $this->cacheId = sha1($this->cacheId . serialize($this->excluded));
636
    }
637
638
639
    /**
640
     * Set the internal filters according to one or more options:
641
     *
642
     * - 'saml2': all SAML2.0-capable entities.
643
     * - 'saml20-idp': all SAML2.0-capable identity providers.
644
     * - 'saml20-sp': all SAML2.0-capable service providers.
645
     * - 'saml20-aa': all SAML2.0-capable attribute authorities.
646
     *
647
     * @param array $set An array of the different roles and protocols to filter by.
648
     */
649
    public function setFilters(array $set): void
650
    {
651
        if (empty($set)) {
652
            return;
653
        }
654
655
        // configure filters
656
        $this->protocols = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array(SimpleSAML\SAML2\C...ants::NS_SAMLP => true) of type array<string,true> is incompatible with the declared type string[] of property $protocols.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
657
            C::NS_SAMLP => true,
658
        ];
659
        $this->roles = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array('SimpleSAML\SAML2\...ityDescriptor' => true) of type array<string,true> is incompatible with the declared type SimpleSAML\SAML2\XML\md\AbstractSSODescriptor[] of property $roles.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
660
            'SimpleSAML\SAML2\XML\md\IDPSSODescriptor'             => true,
661
            'SimpleSAML\SAML2\XML\md\SPSSODescriptor'              => true,
662
            'SimpleSAML\SAML2\XML\md\AttributeAuthorityDescriptor' => true,
663
        ];
664
665
        // now translate from the options we have, to specific protocols and roles
666
667
        // check SAML 2.0 protocol
668
        $options = ['saml2', 'saml20-idp', 'saml20-sp', 'saml20-aa'];
669
        $this->protocols[C::NS_SAMLP] = (array_intersect($set, $options) !== []);
670
671
        // check IdP
672
        $options = ['saml2', 'saml20-idp'];
673
        $this->roles['SimpleSAML\SAML2\XML\md\IDPSSODescriptor'] = (array_intersect($set, $options) !== []);
674
675
        // check SP
676
        $options = ['saml2', 'saml20-sp'];
677
        $this->roles['SimpleSAML\SAML2\XML\md\SPSSODescriptor'] = (array_intersect($set, $options) !== []);
678
679
        // check AA
680
        $options = ['saml2', 'saml20-aa'];
681
        $this->roles['SimpleSAML\SAML2\XML\md\AttributeAuthorityDescriptor'] = (array_intersect($set, $options) !== []);
682
683
        $this->cacheId = sha1($this->cacheId . serialize($this->protocols) . serialize($this->roles));
684
    }
685
686
687
    /**
688
     * Retrieve the complete, signed metadata as text.
689
     *
690
     * This function will write the new metadata to the cache file, but will not return
691
     * the cached metadata.
692
     *
693
     * @return string  The metadata, as text.
694
     */
695
    public function updateCachedMetadata(): string
696
    {
697
        $ed = $this->getEntitiesDescriptor();
698
        $ed = $this->exclude($ed);
699
        $ed = $this->filter($ed);
700
        $this->addSignature($ed);
701
702
        $xml = $ed->toXML();
703
        $xml = $xml->ownerDocument?->saveXML($xml);
704
705
        if ($this->cacheGenerated !== null) {
706
            Logger::debug($this->logLoc . 'Saving generated metadata to cache.');
707
            $now = new DateTimeImmutable('now');
708
            $now = $now->setTimeZone(new DateTimezone('Z'));
709
            $this->addCacheItem($this->cacheId, $xml, $now->add($this->cacheGenerated), $this->cacheTag);
710
        }
711
712
        return $xml;
713
    }
714
715
716
    /**
717
     * Retrieve the complete, signed metadata as text.
718
     *
719
     * @return string  The metadata, as text.
720
     */
721
    public function getMetadata(): string
722
    {
723
        if ($this->cacheGenerated !== null) {
724
            $xml = $this->getCacheItem($this->cacheId, $this->cacheTag);
725
            if ($xml !== null) {
726
                Logger::debug($this->logLoc . 'Loaded generated metadata from cache.');
727
                return $xml;
728
            }
729
        }
730
731
        return $this->updateCachedMetadata();
732
    }
733
734
735
    /**
736
     * Update the cached copy of our metadata.
737
     */
738
    public function updateCache(): void
739
    {
740
        foreach ($this->sources as $source) {
741
            $source->updateCache();
742
        }
743
744
        $this->updateCachedMetadata();
745
    }
746
}
747