Passed
Pull Request — master (#374)
by Tim
02:36
created

EntitiesDescriptor::getEntitiesDescriptors()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\SAML2\XML\md;
6
7
use DOMElement;
8
use SimpleSAML\SAML2\Assert\Assert;
9
use SimpleSAML\SAML2\Exception\ProtocolViolationException;
10
use SimpleSAML\SAML2\Type\SAMLDateTimeValue;
11
use SimpleSAML\SAML2\Type\SAMLStringValue;
12
use SimpleSAML\SAML2\XML\mdrpi\PublicationInfo;
13
use SimpleSAML\SAML2\XML\mdrpi\PublicationPath;
14
use SimpleSAML\SAML2\XML\mdrpi\RegistrationInfo;
15
use SimpleSAML\XML\Constants as C;
16
use SimpleSAML\XML\SchemaValidatableElementInterface;
17
use SimpleSAML\XML\SchemaValidatableElementTrait;
18
use SimpleSAML\XMLSchema\Exception\InvalidDOMElementException;
19
use SimpleSAML\XMLSchema\Exception\TooManyElementsException;
20
use SimpleSAML\XMLSchema\Type\DurationValue;
21
use SimpleSAML\XMLSchema\Type\IDValue;
22
use SimpleSAML\XMLSecurity\XML\ds\Signature;
23
24
use function array_filter;
25
use function array_merge;
26
use function array_values;
27
use function count;
28
29
/**
30
 * Class representing SAML 2 EntitiesDescriptor element.
31
 *
32
 * @package simplesamlphp/saml2
33
 */
34
final class EntitiesDescriptor extends AbstractMetadataDocument implements SchemaValidatableElementInterface
35
{
36
    use SchemaValidatableElementTrait;
37
38
39
    /**
40
     * EntitiesDescriptor constructor.
41
     *
42
     * @param \SimpleSAML\SAML2\XML\md\EntityDescriptor[] $entityDescriptors
43
     * @param \SimpleSAML\SAML2\XML\md\EntitiesDescriptor[] $entitiesDescriptors
44
     * @param \SimpleSAML\SAML2\Type\SAMLStringValue|null $Name
45
     * @param \SimpleSAML\XMLSchema\Type\IDValue|null $ID
46
     * @param \SimpleSAML\SAML2\Type\SAMLDateTimeValue|null $validUntil
47
     * @param \SimpleSAML\XMLSchema\Type\DurationValue|null $cacheDuration
48
     * @param \SimpleSAML\SAML2\XML\md\Extensions|null $extensions
49
     */
50
    public function __construct(
51
        protected array $entityDescriptors = [],
52
        protected array $entitiesDescriptors = [],
53
        protected ?SAMLStringValue $Name = null,
54
        ?IDValue $ID = null,
55
        ?SAMLDateTimeValue $validUntil = null,
56
        ?DurationValue $cacheDuration = null,
57
        ?Extensions $extensions = null,
58
    ) {
59
        Assert::true(
60
            !empty($entitiesDescriptors) || !empty($entityDescriptors),
61
            'At least one md:EntityDescriptor or md:EntitiesDescriptor element is required.',
62
            ProtocolViolationException::class,
63
        );
64
        Assert::maxCount($entitiesDescriptors, C::UNBOUNDED_LIMIT);
65
        Assert::maxCount($entityDescriptors, C::UNBOUNDED_LIMIT);
66
        Assert::allIsInstanceOf($entitiesDescriptors, EntitiesDescriptor::class);
67
        Assert::allIsInstanceOf($entityDescriptors, EntityDescriptor::class);
68
69
        if ($extensions !== null) {
70
            /**
71
             * When a <mdrpi:RegistrationInfo> element appears in the <md:Extensions> element of a
72
             * <md:EntitiesDescriptor> element it applies to all descendant <md:EntitiesDescriptor> and
73
             * <md:EntityDescriptor> elements. That is to say, this is equivalent to putting an identical
74
             * <mdrpi:RegistrationInfo> on every descendant <md:EntityDescriptor>. When used in this
75
             * manner, descendant <md:EntitiesDescriptor> and <md:EntityDescriptor> elements MUST
76
             * NOT contain a <mdrpi:RegistrationInfo> element in their <md:Extensions> element.
77
             */
78
            $toplevel_regInfo = array_values(array_filter($extensions->getList(), function ($ext) {
79
                return $ext instanceof RegistrationInfo;
80
            }));
81
82
            /**
83
             * The <mdrpi:PublicationInfo> element SHOULD only be used on the root element of a metadata document.
84
             */
85
            $toplevel_pubInfo = array_values(array_filter($extensions->getList(), function ($ext) {
86
                return $ext instanceof PublicationInfo;
87
            }));
88
89
            /**
90
             * When a <mdrpi:PublicationPath> element appears in the <md:Extensions> element of a
91
             * <md:EntitiesDescriptor> element it applies to all descendant <md:EntitiesDescriptor> and
92
             * <md:EntityDescriptor> elements. That is to say, this is equivalent to putting an identical
93
             * <mdrpi:PublicationPath> on every descendant <md:EntitiesDescriptor> and <md:EntityDescriptor>.
94
             * When used in this manner, descendant <md:EntitiesDescriptor> and <md:EntityDescriptor>
95
             * elements MUST NOT contain a <mdrpi:PublicationPath> element in their <md:Extensions> element.
96
             */
97
            $toplevel_pubPath = array_values(array_filter($extensions->getList(), function ($ext) {
98
                return $ext instanceof PublicationPath;
99
            }));
100
101
            if (count($toplevel_regInfo) > 0 || count($toplevel_pubInfo) > 0 || count($toplevel_pubPath)) {
102
                $nestedExtensions = [];
103
                foreach (array_merge($entityDescriptors, $entitiesDescriptors) as $ed) {
104
                    $nestedExtensions = array_merge($nestedExtensions, $this->getRecursiveExtensions($ed));
105
                }
106
107
                if (count($toplevel_regInfo) > 0) {
108
                    $nested_regInfo = array_values(array_filter($nestedExtensions, function ($ext) {
109
                        return $ext instanceof RegistrationInfo;
110
                    }));
111
112
                    Assert::count(
113
                        $nested_regInfo,
114
                        0,
115
                        "<mdrpi:RegistrationInfo> already set at top-level.",
116
                        ProtocolViolationException::class,
117
                    );
118
                }
119
120
                if (count($toplevel_pubInfo) > 0) {
121
                    $nested_pubInfo = array_values(array_filter($nestedExtensions, function ($ext) {
122
                        return $ext instanceof PublicationInfo;
123
                    }));
124
125
                    Assert::count(
126
                        $nested_pubInfo,
127
                        0,
128
                        "<mdrpi:PublicationInfo> already set at top-level.",
129
                        ProtocolViolationException::class,
130
                    );
131
                }
132
133
                if (count($toplevel_pubPath) > 0) {
134
                    $nested_pubPath = array_values(array_filter($nestedExtensions, function ($ext) {
135
                        return $ext instanceof PublicationPath;
136
                    }));
137
138
                    Assert::count(
139
                        $nested_pubPath,
140
                        0,
141
                        "<mdrpi:PublicationPath> already set at top-level.",
142
                        ProtocolViolationException::class,
143
                    );
144
                }
145
            }
146
        }
147
148
        parent::__construct($ID, $validUntil, $cacheDuration, $extensions);
149
    }
150
151
152
    /**
153
     * Get all extensions from all nested entity/entities descriptors
154
     */
155
    private function getRecursiveExtensions(EntityDescriptor|EntitiesDescriptor $descriptor): array
156
    {
157
        $extensions = [];
158
        if ($descriptor->getExtensions() !== null) {
159
            $extensions = $descriptor->getExtensions()->getList();
0 ignored issues
show
Bug introduced by
The method getList() does not exist on SimpleSAML\XML\AbstractElement. It seems like you code against a sub-type of SimpleSAML\XML\AbstractElement such as SimpleSAML\SAML2\XML\md\Extensions or SimpleSAML\SAML2\XML\samlp\Extensions. ( Ignorable by Annotation )

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

159
            $extensions = $descriptor->getExtensions()->/** @scrutinizer ignore-call */ getList();
Loading history...
160
161
            if ($descriptor instanceof EntitiesDescriptor) {
162
                $eds = array_merge($descriptor->getEntitiesDescriptors(), $descriptor->getEntityDescriptors());
163
                foreach ($eds as $ed) {
164
                    $extensions = array_merge($extensions, $descriptor->getRecursiveExtensions($ed));
165
                }
166
            }
167
        }
168
169
        return $extensions;
170
    }
171
172
173
    /**
174
     * Get the EntitiesDescriptor children objects
175
     *
176
     * @return \SimpleSAML\SAML2\XML\md\EntitiesDescriptor[]
177
     */
178
    public function getEntitiesDescriptors(): array
179
    {
180
        return $this->entitiesDescriptors;
181
    }
182
183
184
    /**
185
     * Get the EntityDescriptor children objects
186
     *
187
     * @return \SimpleSAML\SAML2\XML\md\EntityDescriptor[]
188
     */
189
    public function getEntityDescriptors(): array
190
    {
191
        return $this->entityDescriptors;
192
    }
193
194
195
    /**
196
     * Collect the value of the Name property.
197
     *
198
     * @return \SimpleSAML\SAML2\Type\SAMLStringValue|null
199
     */
200
    public function getName(): ?SAMLStringValue
201
    {
202
        return $this->Name;
203
    }
204
205
206
    /**
207
     * Initialize an EntitiesDescriptor from an existing XML document.
208
     *
209
     * @param \DOMElement $xml The XML element we should load.
210
     * @return static
211
     *
212
     * @throws \SimpleSAML\XMLSchema\Exception\InvalidDOMElementException
213
     *   if the qualified name of the supplied element is wrong
214
     * @throws \SimpleSAML\XMLSchema\Exception\TooManyElementsException
215
     *   if too many child-elements of a type are specified
216
     */
217
    public static function fromXML(DOMElement $xml): static
218
    {
219
        Assert::same($xml->localName, 'EntitiesDescriptor', InvalidDOMElementException::class);
220
        Assert::same($xml->namespaceURI, EntitiesDescriptor::NS, InvalidDOMElementException::class);
221
222
        $orgs = Organization::getChildrenOfClass($xml);
223
        Assert::maxCount(
224
            $orgs,
225
            1,
226
            'More than one Organization found in this descriptor',
227
            TooManyElementsException::class,
228
        );
229
230
        $extensions = Extensions::getChildrenOfClass($xml);
231
        Assert::maxCount(
232
            $extensions,
233
            1,
234
            'Only one md:Extensions element is allowed.',
235
            TooManyElementsException::class,
236
        );
237
238
        $signature = Signature::getChildrenOfClass($xml);
239
        Assert::maxCount(
240
            $signature,
241
            1,
242
            'Only one ds:Signature element is allowed.',
243
            TooManyElementsException::class,
244
        );
245
246
        $entities = new static(
247
            EntityDescriptor::getChildrenOfClass($xml),
248
            EntitiesDescriptor::getChildrenOfClass($xml),
249
            self::getOptionalAttribute($xml, 'Name', SAMLStringValue::class, null),
250
            self::getOptionalAttribute($xml, 'ID', IDValue::class, null),
251
            self::getOptionalAttribute($xml, 'validUntil', SAMLDateTimeValue::class, null),
252
            self::getOptionalAttribute($xml, 'cacheDuration', DurationValue::class, null),
253
            !empty($extensions) ? $extensions[0] : null,
254
        );
255
256
        if (!empty($signature)) {
257
            $entities->setSignature($signature[0]);
258
            $entities->setXML($xml);
259
        }
260
261
        return $entities;
262
    }
263
264
265
    /**
266
     * Convert this assertion to an unsigned XML document.
267
     * This method does not sign the resulting XML document.
268
     *
269
     * @return \DOMElement The root element of the DOM tree
270
     */
271
    public function toUnsignedXML(?DOMElement $parent = null): DOMElement
272
    {
273
        $e = parent::toUnsignedXML($parent);
274
275
        if ($this->getName() !== null) {
276
            $e->setAttribute('Name', $this->getName()->getValue());
277
        }
278
279
        foreach ($this->getEntitiesDescriptors() as $entitiesDescriptor) {
280
            $entitiesDescriptor->toXML($e);
281
        }
282
283
        foreach ($this->getEntityDescriptors() as $entityDescriptor) {
284
            $entityDescriptor->toXML($e);
285
        }
286
287
        return $e;
288
    }
289
}
290