Passed
Push — master ( 371042...92aa7a )
by Tim
02:43
created

Aggregator::getEntitiesDescriptor()   B

Complexity

Conditions 8
Paths 20

Size

Total Lines 40
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

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

263
        $this->initSources(/** @scrutinizer ignore-type */ $config->getOptionalArray('sources', []));
Loading history...
264
    }
265
266
267
    /**
268
     * Populate the sources array.
269
     *
270
     * This is called from the constructor, and can be overridden in subclasses.
271
     *
272
     * @param array $sources  The sources as an array of \SimpleSAML\Configuration objects.
273
     */
274
    protected function initSources(array $sources): void
275
    {
276
        foreach ($sources as $source) {
277
            $this->sources[] = new EntitySource($this, Configuration::loadFromArray($source));
278
        }
279
    }
280
281
282
    /**
283
     * Return an instance of the aggregator with the given id.
284
     *
285
     * @param string $id  The id of the aggregator.
286
     * @return Aggregator
287
     */
288
    public static function getAggregator(string $id): Aggregator
289
    {
290
        $config = Configuration::getConfig('module_aggregator2.php');
291
        /** @psalm-suppress PossiblyNullArgument */
292
        return new Aggregator($id, $config->getOptionalConfigItem($id, []));
0 ignored issues
show
Bug introduced by
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

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

453
        $key = PrivateKey::fromFile($this->signKey, /** @scrutinizer ignore-type */ $this->signKeyPass);
Loading history...
454
        $signer = (new SignatureAlgorithmFactory())->getAlgorithm(
455
            $this->signAlg,
0 ignored issues
show
Bug introduced by
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

455
            /** @scrutinizer ignore-type */ $this->signAlg,
Loading history...
456
            $key
457
        );
458
459
        $element->sign($signer, C::C14N_EXCLUSIVE_WITHOUT_COMMENTS, $keyInfo ?? []);
0 ignored issues
show
Bug introduced by
It seems like $keyInfo ?? array() can also be of type array; however, parameter $keyInfo of SimpleSAML\XMLSecurity\X...lementInterface::sign() does only seem to accept SimpleSAML\XMLSecurity\XML\ds\KeyInfo|null, 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

459
        $element->sign($signer, C::C14N_EXCLUSIVE_WITHOUT_COMMENTS, /** @scrutinizer ignore-type */ $keyInfo ?? []);
Loading history...
460
    }
461
462
463
    /**
464
     * Recursively browse the children of an EntitiesDescriptor element looking for EntityDescriptor elements, and
465
     * return an array containing all of them.
466
     *
467
     * @param \SAML2\XML\md\EntitiesDescriptor $entity The source EntitiesDescriptor that holds the entities to extract.
468
     *
469
     * @return array An array containing all the EntityDescriptors found.
470
     */
471
    private static function extractEntityDescriptors(EntitiesDescriptor $entity): array
472
    {
473
        $results = [];
474
        foreach ($entity->getChildren() as $child) {
0 ignored issues
show
Bug introduced by
The method getChildren() does not exist on SimpleSAML\SAML2\XML\md\EntitiesDescriptor. Did you maybe mean getChildrenOfClass()? ( Ignorable by Annotation )

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

474
        foreach ($entity->/** @scrutinizer ignore-call */ getChildren() as $child) {

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...
475
            if ($child instanceof EntityDescriptor) {
476
                $results[] = $child;
477
                continue;
478
            }
479
480
            $results = array_merge($results, self::extractEntityDescriptors($child));
481
        }
482
        return $results;
483
    }
484
485
486
    /**
487
     * Retrieve all entities as an EntitiesDescriptor.
488
     *
489
     * @return \SAML2\XML\md\EntitiesDescriptor  The entities.
490
     */
491
    protected function getEntitiesDescriptor(): EntitiesDescriptor
492
    {
493
        $extensions = [];
494
495
        // add RegistrationInfo extension if enabled
496
        if (!empty($this->regInfo)) {
497
            $extensions[] = RegistrationInfo::fromArray($this->regInfo);
498
        }
499
500
        // add PublicationInfo extension if enabled
501
        if (!empty($this->pubInfo)) {
502
            $extensions[] = PublicationInfo::fromArray($this->pubInfo);
503
504
        }
505
506
        $children = [];
507
        foreach ($this->sources as $source) {
508
            $m = $source->getMetadata();
509
            if ($m === null) {
510
                continue;
511
            }
512
513
            if ($m instanceof EntityDescriptor) {
514
                $children[] = $m;
515
            } elseif ($m instanceof EntitiesDescriptor) {
516
                $children = array_merge($children, self::extractEntityDescriptors($m));
517
            }
518
        }
519
        $children = array_unique($children, SORT_REGULAR);
520
521
        $now = new DateTimeImmutable('@' . strval(time() + $this->validLength));
522
        $now = $now->setTimeZone(new DateTimeZone('Z'));
523
524
        $ret = new EntitiesDescriptor(
525
            entityDescriptors: $children,
526
            validUntil: $now,
527
            extensions: empty($extensions) ? null : new Extensions($extensions),
528
        );
529
530
        return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret returns the type SimpleSAML\SAML2\XML\md\EntitiesDescriptor which is incompatible with the documented return type SAML2\XML\md\EntitiesDescriptor.
Loading history...
531
    }
532
533
534
    /**
535
     * Recursively traverse the children of an EntitiesDescriptor, removing those entities listed in the $entities
536
     * property. Returns the EntitiesDescriptor with the entities filtered out.
537
     *
538
     * @param \SAML2\XML\md\EntitiesDescriptor $descriptor The EntitiesDescriptor from where to exclude entities.
539
     *
540
     * @return \SAML2\XML\md\EntitiesDescriptor The EntitiesDescriptor with excluded entities filtered out.
541
     */
542
    protected function exclude(EntitiesDescriptor $descriptor): EntitiesDescriptor
543
    {
544
        if (empty($this->excluded)) {
545
            return $descriptor;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $descriptor returns the type SimpleSAML\SAML2\XML\md\EntitiesDescriptor which is incompatible with the documented return type SAML2\XML\md\EntitiesDescriptor.
Loading history...
546
        }
547
548
        $descriptors = array_merge($descriptor->getEntityDescriptors(), $descriptor->getEntitiesDescriptors());
549
        $filtered = [];
550
        foreach ($descriptors as $child) {
551
            if ($child instanceof EntityDescriptor) {
552
                if (in_array($child->getEntityID(), $this->excluded)) {
553
                    continue;
554
                }
555
                $filtered[] = $child;
556
            }
557
558
            if ($child instanceof EntitiesDescriptor) {
559
                $filtered[] = $this->exclude($child);
560
            }
561
        }
562
563
564
        return new EntitiesDescriptor($filtered);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new SimpleSAML\SA...esDescriptor($filtered) returns the type SimpleSAML\SAML2\XML\md\EntitiesDescriptor which is incompatible with the documented return type SAML2\XML\md\EntitiesDescriptor.
Loading history...
565
    }
566
567
568
    /**
569
     * Recursively traverse the children of an EntitiesDescriptor, keeping only those entities with the roles listed in
570
     * the $roles property, and support for the protocols listed in the $protocols property. Returns the
571
     * EntitiesDescriptor containing only those entities.
572
     *
573
     * @param \SAML2\XML\md\EntitiesDescriptor $descriptor The EntitiesDescriptor to filter.
574
     *
575
     * @return \SAML2\XML\md\EntitiesDescriptor The EntitiesDescriptor with only the entities filtered.
576
     */
577
    protected function filter(EntitiesDescriptor $descriptor): EntitiesDescriptor
578
    {
579
        if (empty($this->roles) || empty($this->protocols)) {
580
            return $descriptor;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $descriptor returns the type SimpleSAML\SAML2\XML\md\EntitiesDescriptor which is incompatible with the documented return type SAML2\XML\md\EntitiesDescriptor.
Loading history...
581
        }
582
583
        $enabled_roles = array_keys($this->roles, true);
584
        $enabled_protos = array_keys($this->protocols, true);
585
586
        $descriptors = array_merge($descriptor->getEntityDescriptors(), $descriptor->getEntitiesDescriptors());
587
        $filtered = [];
588
        foreach ($descriptors as $child) {
589
            if ($child instanceof EntityDescriptor) {
590
                foreach ($child->getRoleDescriptor() as $role) {
591
                    if (in_array(get_class($role), $enabled_roles)) {
592
                        // we found a role descriptor that is enabled by our filters, check protocols
593
                        if (array_intersect($enabled_protos, $role->getProtocolSupportEnumeration()) !== []) {
594
                            // it supports some protocol we have enabled, add it
595
                            $filtered[] = $child;
596
                            break;
597
                        }
598
                    }
599
                }
600
            }
601
602
            if ($child instanceof EntitiesDescriptor) {
603
                $filtered[] = $this->filter($child);
604
            }
605
        }
606
607
        return new EntitiesDescriptor($filtered);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new SimpleSAML\SA...esDescriptor($filtered) returns the type SimpleSAML\SAML2\XML\md\EntitiesDescriptor which is incompatible with the documented return type SAML2\XML\md\EntitiesDescriptor.
Loading history...
608
    }
609
610
611
    /**
612
     * Set this aggregator to exclude a set of entities from the resulting aggregate.
613
     *
614
     * @param array $entities The entity IDs of the entities to exclude.
615
     */
616
    public function excludeEntities(array $entities): void
617
    {
618
        if (empty($entities)) {
619
            return;
620
        }
621
        $this->excluded = $entities;
622
        sort($this->excluded);
623
        $this->cacheId = sha1($this->cacheId . serialize($this->excluded));
624
    }
625
626
627
    /**
628
     * Set the internal filters according to one or more options:
629
     *
630
     * - 'saml2': all SAML2.0-capable entities.
631
     * - 'saml20-idp': all SAML2.0-capable identity providers.
632
     * - 'saml20-sp': all SAML2.0-capable service providers.
633
     * - 'saml20-aa': all SAML2.0-capable attribute authorities.
634
     *
635
     * @param array $set An array of the different roles and protocols to filter by.
636
     */
637
    public function setFilters(array $set): void
638
    {
639
        if (empty($set)) {
640
            return;
641
        }
642
643
        // configure filters
644
        $this->protocols = [
645
            C::NS_SAMLP => true,
646
        ];
647
        $this->roles = [
648
            'SimpleSAML\SAML2\XML\md\IDPSSODescriptor'             => true,
649
            'SimpleSAML\SAML2\XML\md\SPSSODescriptor'              => true,
650
            'SimpleSAML\SAML2\XML\md\AttributeAuthorityDescriptor' => true,
651
        ];
652
653
        // now translate from the options we have, to specific protocols and roles
654
655
        // check SAML 2.0 protocol
656
        $options = ['saml2', 'saml20-idp', 'saml20-sp', 'saml20-aa'];
657
        $this->protocols[C::NS_SAMLP] = (array_intersect($set, $options) !== []);
658
659
        // check IdP
660
        $options = ['saml2', 'saml20-idp'];
661
        $this->roles['SimpleSAML\SAML2\XML\md\IDPSSODescriptor'] = (array_intersect($set, $options) !== []);
662
663
        // check SP
664
        $options = ['saml2', 'saml20-sp'];
665
        $this->roles['SimpleSAML\SAML2\XML\md\SPSSODescriptor'] = (array_intersect($set, $options) !== []);
666
667
        // check AA
668
        $options = ['saml2', 'saml20-aa'];
669
        $this->roles['SimpleSAML\SAML2\XML\md\AttributeAuthorityDescriptor'] = (array_intersect($set, $options) !== []);
670
671
        $this->cacheId = sha1($this->cacheId . serialize($this->protocols) . serialize($this->roles));
672
    }
673
674
675
    /**
676
     * Retrieve the complete, signed metadata as text.
677
     *
678
     * This function will write the new metadata to the cache file, but will not return
679
     * the cached metadata.
680
     *
681
     * @return string  The metadata, as text.
682
     */
683
    public function updateCachedMetadata(): string
684
    {
685
        $ed = $this->getEntitiesDescriptor();
686
        $ed = $this->exclude($ed);
687
        $ed = $this->filter($ed);
688
        $this->addSignature($ed);
689
690
        $xml = $ed->toXML();
691
        $xml = $xml->ownerDocument->saveXML($xml);
0 ignored issues
show
Bug introduced by
The method saveXML() does not exist on null. ( Ignorable by Annotation )

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

691
        /** @scrutinizer ignore-call */ 
692
        $xml = $xml->ownerDocument->saveXML($xml);

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...
692
693
        if ($this->cacheGenerated !== null) {
694
            Logger::debug($this->logLoc . 'Saving generated metadata to cache.');
695
            $this->addCacheItem($this->cacheId, $xml, time() + $this->cacheGenerated, $this->cacheTag);
696
        }
697
698
        return $xml;
699
    }
700
701
702
    /**
703
     * Retrieve the complete, signed metadata as text.
704
     *
705
     * @return string  The metadata, as text.
706
     */
707
    public function getMetadata(): string
708
    {
709
        if ($this->cacheGenerated !== null) {
710
            $xml = $this->getCacheItem($this->cacheId, $this->cacheTag);
711
            if ($xml !== null) {
712
                Logger::debug($this->logLoc . 'Loaded generated metadata from cache.');
713
                return $xml;
714
            }
715
        }
716
717
        return $this->updateCachedMetadata();
718
    }
719
720
721
    /**
722
     * Update the cached copy of our metadata.
723
     */
724
    public function updateCache(): void
725
    {
726
        foreach ($this->sources as $source) {
727
            $source->updateCache();
728
        }
729
730
        $this->updateCachedMetadata();
731
    }
732
}
733