Passed
Pull Request — master (#280)
by Tim
02:13
created

EntityDescriptor::toUnsignedXML()   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 InvalidArgumentException;
9
use SimpleSAML\Assert\Assert;
10
use SimpleSAML\SAML2\Constants;
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
        if (empty($roleDescriptors) && $affiliationDescriptor === null) {
103
            throw new InvalidArgumentException(
104
                'Must have either one of the RoleDescriptors or an AffiliationDescriptor in EntityDescriptor.'
105
            );
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 !== Constants::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
        if (empty($roleDescriptors) && is_null($affiliationDescriptor)) {
197
            throw new InvalidArgumentException(
198
                'Must have either one of the RoleDescriptors or an AffiliationDescriptor in EntityDescriptor.'
199
            );
200
        } elseif (!empty($roleDescriptors) && !is_null($affiliationDescriptor)) {
201
            throw new InvalidArgumentException(
202
                'AffiliationDescriptor cannot be combined with other RoleDescriptor elements in EntityDescriptor.'
203
            );
204
        }
205
206
        $entity = new self(
207
            $entityID,
208
            self::getAttribute($xml, 'ID', null),
209
            $validUntil !== null ? XMLUtils::xsDateTimeToTimestamp($validUntil) : null,
210
            self::getAttribute($xml, 'cacheDuration', null),
211
            !empty($extensions) ? $extensions[0] : null,
212
            $roleDescriptors,
213
            $affiliationDescriptor,
214
            $organization,
215
            $contactPersons,
216
            $additionalMetadataLocation,
217
            self::getAttributesNSFromXML($xml)
218
        );
219
220
        if (!empty($signature)) {
221
            $entity->setSignature($signature[0]);
222
        }
223
224
        $entity->setXML($xml);
225
226
        return $entity;
227
    }
228
229
230
    /**
231
     * Collect the value of the entityID property.
232
     *
233
     * @return string
234
     * @throws \SimpleSAML\Assert\AssertionFailedException
235
     */
236
    public function getEntityID(): string
237
    {
238
        Assert::notEmpty($this->entityID);
239
240
        return $this->entityID;
241
    }
242
243
244
    /**
245
     * Set the value of the entityID-property
246
     * @param string $entityId
247
     */
248
    protected function setEntityID(string $entityId): void
249
    {
250
        Assert::notEmpty($entityId, 'The entityID attribute cannot be empty.');
251
        Assert::maxLength($entityId, 1024, 'The entityID attribute cannot be longer than 1024 characters.');
252
        $this->entityID = $entityId;
253
    }
254
255
256
    /**
257
     * Collect the value of the RoleDescriptor property.
258
     *
259
     * @return \SimpleSAML\SAML2\XML\md\AbstractRoleDescriptor[]
260
     */
261
    public function getRoleDescriptors(): array
262
    {
263
        return $this->RoleDescriptor;
264
    }
265
266
267
    /**
268
     * Set the value of the RoleDescriptor property.
269
     *
270
     * @param \SimpleSAML\SAML2\XML\md\AbstractRoleDescriptor[] $roleDescriptors
271
     * @throws \SimpleSAML\Assert\AssertionFailedException
272
     */
273
    protected function setRoleDescriptors(array $roleDescriptors): void
274
    {
275
        Assert::allIsInstanceOf(
276
            $roleDescriptors,
277
            AbstractRoleDescriptor::class,
278
            'All role descriptors must extend AbstractRoleDescriptor.'
279
        );
280
        $this->RoleDescriptor = $roleDescriptors;
281
    }
282
283
284
    /**
285
     * Collect the value of the AffiliationDescriptor property.
286
     *
287
     * @return \SimpleSAML\SAML2\XML\md\AffiliationDescriptor|null
288
     */
289
    public function getAffiliationDescriptor(): ?AffiliationDescriptor
290
    {
291
        return $this->AffiliationDescriptor;
292
    }
293
294
295
    /**
296
     * Set the value of the AffliationDescriptor property.
297
     *
298
     * @param \SimpleSAML\SAML2\XML\md\AffiliationDescriptor|null $affiliationDescriptor
299
     */
300
    protected function setAffiliationDescriptor(?AffiliationDescriptor $affiliationDescriptor = null): void
301
    {
302
        $this->AffiliationDescriptor = $affiliationDescriptor;
303
    }
304
305
306
    /**
307
     * Collect the value of the Organization property.
308
     *
309
     * @return \SimpleSAML\SAML2\XML\md\Organization|null
310
     */
311
    public function getOrganization(): ?Organization
312
    {
313
        return $this->Organization;
314
    }
315
316
317
    /**
318
     * Set the value of the Organization property.
319
     *
320
     * @param \SimpleSAML\SAML2\XML\md\Organization|null $organization
321
     */
322
    protected function setOrganization(?Organization $organization = null): void
323
    {
324
        $this->Organization = $organization;
325
    }
326
327
328
    /**
329
     * Collect the value of the ContactPerson property.
330
     *
331
     * @return \SimpleSAML\SAML2\XML\md\ContactPerson[]
332
     */
333
    public function getContactPersons(): array
334
    {
335
        return $this->ContactPerson;
336
    }
337
338
339
    /**
340
     * Set the value of the ContactPerson property.
341
     *
342
     * @param array $contactPerson
343
     * @throws \SimpleSAML\Assert\AssertionFailedException
344
     */
345
    protected function setContactPersons(array $contactPerson): void
346
    {
347
        Assert::allIsInstanceOf(
348
            $contactPerson,
349
            ContactPerson::class,
350
            'All md:ContactPerson elements must be an instance of ContactPerson.'
351
        );
352
        $this->ContactPerson = $contactPerson;
353
    }
354
355
356
    /**
357
     * Collect the value of the AdditionalMetadataLocation property.
358
     *
359
     * @return \SimpleSAML\SAML2\XML\md\AdditionalMetadataLocation[]
360
     */
361
    public function getAdditionalMetadataLocations(): array
362
    {
363
        return $this->AdditionalMetadataLocation;
364
    }
365
366
367
    /**
368
     * Set the value of the AdditionalMetadataLocation property.
369
     *
370
     * @param array $additionalMetadataLocation
371
     * @throws \SimpleSAML\Assert\AssertionFailedException
372
     */
373
    protected function setAdditionalMetadataLocations(array $additionalMetadataLocation): void
374
    {
375
        Assert::allIsInstanceOf(
376
            $additionalMetadataLocation,
377
            AdditionalMetadataLocation::class,
378
            'All md:AdditionalMetadataLocation elements must be an instance of AdditionalMetadataLocation'
379
        );
380
        $this->AdditionalMetadataLocation = $additionalMetadataLocation;
381
    }
382
383
384
    /**
385
     * Convert this descriptor to an unsigned XML document.
386
     * This method does not sign the resulting XML document.
387
     *
388
     * @param \DOMElement|null $parent
389
     * @return \DOMElement The root element of the DOM tree
390
     */
391
    protected function toUnsignedXML(DOMElement $parent = null): DOMElement
392
    {
393
        $e = parent::toUnsignedXML($parent);
394
        $e->setAttribute('entityID', $this->entityID);
395
396
        foreach ($this->getAttributesNS() as $attr) {
397
            $e->setAttributeNS($attr['namespaceURI'], $attr['qualifiedName'], $attr['value']);
398
        }
399
400
        foreach ($this->RoleDescriptor as $n) {
401
            $n->toXML($e);
402
        }
403
404
        if ($this->AffiliationDescriptor !== null) {
405
            $this->AffiliationDescriptor->toXML($e);
406
        }
407
408
        if ($this->Organization !== null) {
409
            $this->Organization->toXML($e);
410
        }
411
412
        foreach ($this->ContactPerson as $cp) {
413
            $cp->toXML($e);
414
        }
415
416
        foreach ($this->AdditionalMetadataLocation as $n) {
417
            $n->toXML($e);
418
        }
419
420
        return $e;
421
    }
422
}
423