Passed
Pull Request — master (#285)
by Tim
02:20
created

EntityDescriptor::toXML()   B

Complexity

Conditions 7
Paths 64

Size

Total Lines 30
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 15
nc 64
nop 1
dl 0
loc 30
rs 8.8333
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\Assert\Assert;
9
use SimpleSAML\SAML2\Constants as C;
10
use SimpleSAML\SAML2\Exception\ProtocolViolationException;
11
use SimpleSAML\XML\Exception\InvalidDOMElementException;
12
use SimpleSAML\XML\Exception\TooManyElementsException;
13
use SimpleSAML\XML\Utils as XMLUtils;
14
use SimpleSAML\XMLSecurity\XML\ds\Signature;
15
16
use function is_null;
17
18
/**
19
 * Class representing SAML 2 EntityDescriptor element.
20
 *
21
 * @package simplesamlphp/saml2
22
 */
23
final class EntityDescriptor extends AbstractMetadataDocument
24
{
25
    /**
26
     * The entityID this EntityDescriptor represents.
27
     *
28
     * @var string
29
     */
30
    protected string $entityID;
31
32
    /**
33
     * Array with all roles for this entity.
34
     *
35
     * Array of \SimpleSAML\SAML2\XML\md\RoleDescriptor objects (and subclasses of RoleDescriptor).
36
     *
37
     * @var \SimpleSAML\SAML2\XML\md\AbstractRoleDescriptor[]
38
     */
39
    protected array $RoleDescriptor = [];
40
41
    /**
42
     * AffiliationDescriptor of this entity.
43
     *
44
     * @var \SimpleSAML\SAML2\XML\md\AffiliationDescriptor|null
45
     */
46
    protected ?AffiliationDescriptor $AffiliationDescriptor = null;
47
48
    /**
49
     * Organization of this entity.
50
     *
51
     * @var \SimpleSAML\SAML2\XML\md\Organization|null
52
     */
53
    protected ?Organization $Organization = null;
54
55
    /**
56
     * ContactPerson elements for this entity.
57
     *
58
     * @var \SimpleSAML\SAML2\XML\md\ContactPerson[]
59
     */
60
    protected array $ContactPerson = [];
61
62
    /**
63
     * AdditionalMetadataLocation elements for this entity.
64
     *
65
     * @var \SimpleSAML\SAML2\XML\md\AdditionalMetadataLocation[]
66
     */
67
    protected array $AdditionalMetadataLocation = [];
68
69
70
    /**
71
     * Initialize an EntitiyDescriptor.
72
     *
73
     * @param string $entityID The entityID of the entity described by this descriptor.
74
     * @param string|null $id The ID for this document. Defaults to null.
75
     * @param int|null $validUntil Unix time of validify for this document. Defaults to null.
76
     * @param string|null $cacheDuration Maximum time this document can be cached. Defaults to null.
77
     * @param \SimpleSAML\SAML2\XML\md\Extensions|null $extensions An array of extensions.
78
     * @param \SimpleSAML\SAML2\XML\md\AbstractRoleDescriptor[] $roleDescriptors An array of role descriptors.
79
     * @param \SimpleSAML\SAML2\XML\md\AffiliationDescriptor|null $affiliationDescriptor An affiliation descriptor to
80
     *   use instead of role descriptors.
81
     * @param \SimpleSAML\SAML2\XML\md\Organization|null $organization The organization responsible for the SAML entity.
82
     * @param \SimpleSAML\SAML2\XML\md\ContactPerson[] $contacts A list of contact persons for this SAML entity.
83
     * @param \SimpleSAML\SAML2\XML\md\AdditionalMetadataLocation[] $additionalMdLocations A list of
84
     *   additional metadata locations.
85
     * @param \DOMAttr[] $namespacedAttributes
86
     *
87
     * @throws \Exception
88
     */
89
    public function __construct(
90
        string $entityID,
91
        ?string $id = null,
92
        ?int $validUntil = null,
93
        ?string $cacheDuration = null,
94
        Extensions $extensions = null,
95
        array $roleDescriptors = [],
96
        ?AffiliationDescriptor $affiliationDescriptor = null,
97
        ?Organization $organization = null,
98
        array $contacts = [],
99
        array $additionalMdLocations = [],
100
        array $namespacedAttributes = []
101
    ) {
102
        Assert::false(
103
            (empty($roleDescriptors) && $affiliationDescriptor === null),
104
            'Must have either one of the RoleDescriptors or an AffiliationDescriptor in EntityDescriptor.',
105
            ProtocolViolationException::class,
106
        );
107
108
        parent::__construct($id, $validUntil, $cacheDuration, $extensions, $namespacedAttributes);
109
110
        $this->setEntityID($entityID);
111
        $this->setRoleDescriptors($roleDescriptors);
112
        $this->setAffiliationDescriptor($affiliationDescriptor);
113
        $this->setOrganization($organization);
114
        $this->setContactPersons($contacts);
115
        $this->setAdditionalMetadataLocations($additionalMdLocations);
116
    }
117
118
119
    /**
120
     * Convert an existing XML into an EntityDescriptor object
121
     *
122
     * @param \DOMElement $xml An existing EntityDescriptor XML document.
123
     * @return \SimpleSAML\SAML2\XML\md\EntityDescriptor An object representing the given document.
124
     *
125
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException if the qualified name of the supplied element is wrong
126
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException if the supplied element is missing one of the mandatory attributes
127
     * @throws \SimpleSAML\XML\Exception\TooManyElementsException if too many child-elements of a type are specified
128
     */
129
    public static function fromXML(DOMElement $xml): object
130
    {
131
        Assert::same($xml->localName, 'EntityDescriptor', InvalidDOMElementException::class);
132
        Assert::same($xml->namespaceURI, EntityDescriptor::NS, InvalidDOMElementException::class);
133
134
        $validUntil = self::getAttribute($xml, 'validUntil', null);
135
        $extensions = Extensions::getChildrenOfClass($xml);
136
        Assert::maxCount($extensions, 1, 'Only one md:Extensions element is allowed.', TooManyElementsException::class);
137
138
        $signature = Signature::getChildrenOfClass($xml);
139
        Assert::maxCount($signature, 1, 'Only one ds:Signature element is allowed.', TooManyElementsException::class);
140
141
        $entityID = self::getAttribute($xml, 'entityID');
142
        $roleDescriptors = [];
143
        $affiliationDescriptor = null;
144
        $organization = null;
145
        $contactPersons = [];
146
        $additionalMetadataLocation = [];
147
        foreach ($xml->childNodes as $node) {
148
            if (
149
                !($node instanceof DOMElement)
150
                || ($node->namespaceURI !== C::NS_MD)
151
            ) {
152
                continue;
153
            }
154
155
            switch ($node->localName) {
156
                case 'Extensions':
157
                    continue 2;
158
                case 'IDPSSODescriptor':
159
                    $roleDescriptors[] = IDPSSODescriptor::fromXML($node);
160
                    break;
161
                case 'SPSSODescriptor':
162
                    $roleDescriptors[] = SPSSODescriptor::fromXML($node);
163
                    break;
164
                case 'AuthnAuthorityDescriptor':
165
                    $roleDescriptors[] = AuthnAuthorityDescriptor::fromXML($node);
166
                    break;
167
                case 'AttributeAuthorityDescriptor':
168
                    $roleDescriptors[] = AttributeAuthorityDescriptor::fromXML($node);
169
                    break;
170
                case 'PDPDescriptor':
171
                    $roleDescriptors[] = PDPDescriptor::fromXML($node);
172
                    break;
173
                case 'AffiliationDescriptor':
174
                    if ($affiliationDescriptor !== null) {
175
                        throw new TooManyElementsException('More than one AffiliationDescriptor in the entity.');
176
                    }
177
                    $affiliationDescriptor = AffiliationDescriptor::fromXML($node);
178
                    break;
179
                case 'Organization':
180
                    if ($organization !== null) {
181
                        throw new TooManyElementsException('More than one Organization in the entity.');
182
                    }
183
                    $organization = Organization::fromXML($node);
184
                    break;
185
                case 'ContactPerson':
186
                    $contactPersons[] = ContactPerson::fromXML($node);
187
                    break;
188
                case 'AdditionalMetadataLocation':
189
                    $additionalMetadataLocation[] = AdditionalMetadataLocation::fromXML($node);
190
                    break;
191
                default:
192
                    $roleDescriptors[] = UnknownRoleDescriptor::fromXML($node);
193
            }
194
        }
195
196
        Assert::false(
197
            empty($roleDescriptors) && is_null($affiliationDescriptor),
198
            'Must have either one of the RoleDescriptors or an AffiliationDescriptor in EntityDescriptor.',
199
            ProtocolViolationException::class,
200
        );
201
        Assert::false(
202
            !empty($roleDescriptors) && !is_null($affiliationDescriptor),
203
            'AffiliationDescriptor cannot be combined with other RoleDescriptor elements in EntityDescriptor.',
204
            ProtocolViolationException::class,
205
        );
206
207
        $entity = new self(
208
            $entityID,
209
            self::getAttribute($xml, 'ID', null),
210
            $validUntil !== null ? XMLUtils::xsDateTimeToTimestamp($validUntil) : null,
211
            self::getAttribute($xml, 'cacheDuration', null),
212
            !empty($extensions) ? $extensions[0] : null,
213
            $roleDescriptors,
214
            $affiliationDescriptor,
215
            $organization,
216
            $contactPersons,
217
            $additionalMetadataLocation,
218
            self::getAttributesNSFromXML($xml)
219
        );
220
221
        if (!empty($signature)) {
222
            $entity->setSignature($signature[0]);
223
        }
224
225
        return $entity;
226
    }
227
228
229
    /**
230
     * Collect the value of the entityID property.
231
     *
232
     * @return string
233
     * @throws \SimpleSAML\Assert\AssertionFailedException
234
     */
235
    public function getEntityID(): string
236
    {
237
        Assert::notEmpty($this->entityID);
238
239
        return $this->entityID;
240
    }
241
242
243
    /**
244
     * Set the value of the entityID-property
245
     * @param string $entityId
246
     */
247
    protected function setEntityID(string $entityId): void
248
    {
249
        Assert::notEmpty($entityId, 'The entityID attribute cannot be empty.');
250
        Assert::maxLength($entityId, C::ENTITYID_MAX_LENGTH, 'The entityID attribute cannot be longer than 1024 characters.');
251
        $this->entityID = $entityId;
252
    }
253
254
255
    /**
256
     * Collect the value of the RoleDescriptor property.
257
     *
258
     * @return \SimpleSAML\SAML2\XML\md\AbstractRoleDescriptor[]
259
     */
260
    public function getRoleDescriptors(): array
261
    {
262
        return $this->RoleDescriptor;
263
    }
264
265
266
    /**
267
     * Set the value of the RoleDescriptor property.
268
     *
269
     * @param \SimpleSAML\SAML2\XML\md\AbstractRoleDescriptor[] $roleDescriptors
270
     * @throws \SimpleSAML\Assert\AssertionFailedException
271
     */
272
    protected function setRoleDescriptors(array $roleDescriptors): void
273
    {
274
        Assert::allIsInstanceOf(
275
            $roleDescriptors,
276
            AbstractRoleDescriptor::class,
277
            'All role descriptors must extend AbstractRoleDescriptor.'
278
        );
279
        $this->RoleDescriptor = $roleDescriptors;
280
    }
281
282
283
    /**
284
     * Collect the value of the AffiliationDescriptor property.
285
     *
286
     * @return \SimpleSAML\SAML2\XML\md\AffiliationDescriptor|null
287
     */
288
    public function getAffiliationDescriptor(): ?AffiliationDescriptor
289
    {
290
        return $this->AffiliationDescriptor;
291
    }
292
293
294
    /**
295
     * Set the value of the AffliationDescriptor property.
296
     *
297
     * @param \SimpleSAML\SAML2\XML\md\AffiliationDescriptor|null $affiliationDescriptor
298
     */
299
    protected function setAffiliationDescriptor(?AffiliationDescriptor $affiliationDescriptor = null): void
300
    {
301
        $this->AffiliationDescriptor = $affiliationDescriptor;
302
    }
303
304
305
    /**
306
     * Collect the value of the Organization property.
307
     *
308
     * @return \SimpleSAML\SAML2\XML\md\Organization|null
309
     */
310
    public function getOrganization(): ?Organization
311
    {
312
        return $this->Organization;
313
    }
314
315
316
    /**
317
     * Set the value of the Organization property.
318
     *
319
     * @param \SimpleSAML\SAML2\XML\md\Organization|null $organization
320
     */
321
    protected function setOrganization(?Organization $organization = null): void
322
    {
323
        $this->Organization = $organization;
324
    }
325
326
327
    /**
328
     * Collect the value of the ContactPerson property.
329
     *
330
     * @return \SimpleSAML\SAML2\XML\md\ContactPerson[]
331
     */
332
    public function getContactPersons(): array
333
    {
334
        return $this->ContactPerson;
335
    }
336
337
338
    /**
339
     * Set the value of the ContactPerson property.
340
     *
341
     * @param array $contactPerson
342
     * @throws \SimpleSAML\Assert\AssertionFailedException
343
     */
344
    protected function setContactPersons(array $contactPerson): void
345
    {
346
        Assert::allIsInstanceOf(
347
            $contactPerson,
348
            ContactPerson::class,
349
            'All md:ContactPerson elements must be an instance of ContactPerson.'
350
        );
351
        $this->ContactPerson = $contactPerson;
352
    }
353
354
355
    /**
356
     * Collect the value of the AdditionalMetadataLocation property.
357
     *
358
     * @return \SimpleSAML\SAML2\XML\md\AdditionalMetadataLocation[]
359
     */
360
    public function getAdditionalMetadataLocations(): array
361
    {
362
        return $this->AdditionalMetadataLocation;
363
    }
364
365
366
    /**
367
     * Set the value of the AdditionalMetadataLocation property.
368
     *
369
     * @param array $additionalMetadataLocation
370
     * @throws \SimpleSAML\Assert\AssertionFailedException
371
     */
372
    protected function setAdditionalMetadataLocations(array $additionalMetadataLocation): void
373
    {
374
        Assert::allIsInstanceOf(
375
            $additionalMetadataLocation,
376
            AdditionalMetadataLocation::class,
377
            'All md:AdditionalMetadataLocation elements must be an instance of AdditionalMetadataLocation'
378
        );
379
        $this->AdditionalMetadataLocation = $additionalMetadataLocation;
380
    }
381
382
383
    /**
384
     * Create this EntityDescriptor.
385
     *
386
     * @param \DOMElement|null $parent The EntitiesDescriptor we should append this EntityDescriptor to.
387
     * @return \DOMElement
388
     * @throws \Exception
389
     */
390
    public function toXML(DOMElement $parent = null): DOMElement
391
    {
392
        $e = parent::toXML($parent);
393
        $e->setAttribute('entityID', $this->entityID);
394
395
        foreach ($this->getAttributesNS() as $attr) {
396
            $e->setAttributeNS($attr['namespaceURI'], $attr['qualifiedName'], $attr['value']);
397
        }
398
399
        foreach ($this->RoleDescriptor as $n) {
400
            $n->toXML($e);
401
        }
402
403
        if ($this->AffiliationDescriptor !== null) {
404
            $this->AffiliationDescriptor->toXML($e);
405
        }
406
407
        if ($this->Organization !== null) {
408
            $this->Organization->toXML($e);
409
        }
410
411
        foreach ($this->ContactPerson as $cp) {
412
            $cp->toXML($e);
413
        }
414
415
        foreach ($this->AdditionalMetadataLocation as $n) {
416
            $n->toXML($e);
417
        }
418
419
        return $this->signElement($e);
420
    }
421
}
422