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

SPSSODescriptor::toUnsignedXML()   B

Complexity

Conditions 7
Paths 16

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 10
nc 16
nop 1
dl 0
loc 21
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;
10
use SimpleSAML\XML\Exception\InvalidDOMElementException;
11
use SimpleSAML\XML\Exception\TooManyElementsException;
12
use SimpleSAML\XML\Utils as XMLUtils;
13
use SimpleSAML\XMLSecurity\XML\ds\Signature;
14
15
use function array_filter;
16
use function is_bool;
17
use function preg_split;
18
19
/**
20
 * Class representing SAML 2 SPSSODescriptor.
21
 *
22
 * @package simplesamlphp/saml2
23
 */
24
final class SPSSODescriptor extends AbstractSSODescriptor
25
{
26
    /**
27
     * Whether this SP signs authentication requests.
28
     *
29
     * @var bool|null
30
     */
31
    protected ?bool $authnRequestsSigned = null;
32
33
    /**
34
     * Whether this SP wants the Assertion elements to be signed.
35
     *
36
     * @var bool|null
37
     */
38
    protected ?bool $wantAssertionsSigned = null;
39
40
    /**
41
     * List of AssertionConsumerService endpoints for this SP.
42
     *
43
     * Array with IndexedEndpointType objects.
44
     *
45
     * @var \SimpleSAML\SAML2\XML\md\AssertionConsumerService[]
46
     */
47
    protected array $assertionConsumerService = [];
48
49
    /**
50
     * List of AttributeConsumingService descriptors for this SP.
51
     *
52
     * Array with \SimpleSAML\SAML2\XML\md\AttributeConsumingService objects.
53
     *
54
     * @var \SimpleSAML\SAML2\XML\md\AttributeConsumingService[]
55
     */
56
    protected array $attributeConsumingService = [];
57
58
59
    /**
60
     * SPSSODescriptor constructor.
61
     *
62
     * @param \SimpleSAML\SAML2\XML\md\AssertionConsumerService[] $assertionConsumerService
63
     * @param string[] $protocolSupportEnumeration
64
     * @param bool|null $authnRequestsSigned
65
     * @param bool|null $wantAssertionsSigned
66
     * @param \SimpleSAML\SAML2\XML\md\AttributeConsumingService[] $attributeConsumingService
67
     * @param string|null $ID
68
     * @param int|null $validUntil
69
     * @param string|null $cacheDuration
70
     * @param \SimpleSAML\SAML2\XML\md\Extensions|null $extensions
71
     * @param string|null $errorURL
72
     * @param \SimpleSAML\SAML2\XML\md\KeyDescriptor[] $keyDescriptors
73
     * @param \SimpleSAML\SAML2\XML\md\Organization|null $organization
74
     * @param \SimpleSAML\SAML2\XML\md\ContactPerson[] $contacts
75
     * @param \SimpleSAML\SAML2\XML\md\ArtifactResolutionService[] $artifactResolutionService
76
     * @param \SimpleSAML\SAML2\XML\md\SingleLogoutService[] $singleLogoutService
77
     * @param \SimpleSAML\SAML2\XML\md\ManageNameIDService[] $manageNameIDService
78
     * @param \SimpleSAML\SAML2\XML\md\NameIDFormat[] $nameIDFormat
79
     */
80
    public function __construct(
81
        array $assertionConsumerService,
82
        array $protocolSupportEnumeration,
83
        ?bool $authnRequestsSigned = null,
84
        ?bool $wantAssertionsSigned = null,
85
        array $attributeConsumingService = [],
86
        ?string $ID = null,
87
        ?int $validUntil = null,
88
        ?string $cacheDuration = null,
89
        ?Extensions $extensions = null,
90
        ?string $errorURL = null,
91
        array $keyDescriptors = [],
92
        ?Organization $organization = null,
93
        array $contacts = [],
94
        array $artifactResolutionService = [],
95
        array $singleLogoutService = [],
96
        array $manageNameIDService = [],
97
        array $nameIDFormat = []
98
    ) {
99
        parent::__construct(
100
            $protocolSupportEnumeration,
101
            $ID,
102
            $validUntil,
103
            $cacheDuration,
104
            $extensions,
105
            $errorURL,
106
            $keyDescriptors,
107
            $organization,
108
            $contacts,
109
            $artifactResolutionService,
110
            $singleLogoutService,
111
            $manageNameIDService,
112
            $nameIDFormat
113
        );
114
115
        $this->setAssertionConsumerService($assertionConsumerService);
116
        $this->setAuthnRequestsSigned($authnRequestsSigned);
117
        $this->setWantAssertionsSigned($wantAssertionsSigned);
118
        $this->setAttributeConsumingService($attributeConsumingService);
119
120
        // test that only one ACS is marked as default
121
        Assert::maxCount(
122
            array_filter(
123
                $this->getAttributeConsumingService(),
124
                function (AttributeConsumingService $acs) {
125
                    return $acs->getIsDefault() === true;
126
                }
127
            ),
128
            1,
129
            'Only one md:AttributeConsumingService can be set as default.'
130
        );
131
    }
132
133
134
    /**
135
     * Collect the value of the AuthnRequestsSigned-property
136
     *
137
     * @return bool|null
138
     */
139
    public function getAuthnRequestsSigned(): ?bool
140
    {
141
        return $this->authnRequestsSigned;
142
    }
143
144
145
    /**
146
     * Set the value of the AuthnRequestsSigned-property
147
     *
148
     * @param bool|null $flag
149
     */
150
    private function setAuthnRequestsSigned(?bool $flag): void
151
    {
152
        $this->authnRequestsSigned = $flag;
153
    }
154
155
156
    /**
157
     * Collect the value of the WantAssertionsSigned-property
158
     *
159
     * @return bool|null
160
     */
161
    public function getWantAssertionsSigned(): ?bool
162
    {
163
        return $this->wantAssertionsSigned;
164
    }
165
166
167
    /**
168
     * Set the value of the WantAssertionsSigned-property
169
     *
170
     * @param bool|null $flag
171
     */
172
    private function setWantAssertionsSigned(?bool $flag): void
173
    {
174
        $this->wantAssertionsSigned = $flag;
175
    }
176
177
178
    /**
179
     * Collect the value of the AssertionConsumerService-property
180
     *
181
     * @return \SimpleSAML\SAML2\XML\md\AssertionConsumerService[]
182
     */
183
    public function getAssertionConsumerService(): array
184
    {
185
        return $this->assertionConsumerService;
186
    }
187
188
189
    /**
190
     * Set the value of the AssertionConsumerService-property
191
     *
192
     * @param \SimpleSAML\SAML2\XML\md\AssertionConsumerService[] $acs
193
     * @throws \SimpleSAML\Assert\AssertionFailedException
194
     */
195
    private function setAssertionConsumerService(array $acs): void
196
    {
197
        Assert::minCount($acs, 1, 'At least one AssertionConsumerService must be specified.');
198
        Assert::allIsInstanceOf(
199
            $acs,
200
            AssertionConsumerService::class,
201
            'All md:AssertionConsumerService endpoints must be an instance of AssertionConsumerService.'
202
        );
203
        $this->assertionConsumerService = $acs;
204
    }
205
206
207
    /**
208
     * Collect the value of the AttributeConsumingService-property
209
     *
210
     * @return \SimpleSAML\SAML2\XML\md\AttributeConsumingService[]
211
     */
212
    public function getAttributeConsumingService(): array
213
    {
214
        return $this->attributeConsumingService;
215
    }
216
217
218
    /**
219
     * Set the value of the AttributeConsumingService-property
220
     *
221
     * @param \SimpleSAML\SAML2\XML\md\AttributeConsumingService[] $acs
222
     * @throws \SimpleSAML\Assert\AssertionFailedException
223
     */
224
    private function setAttributeConsumingService(array $acs): void
225
    {
226
        Assert::allIsInstanceOf(
227
            $acs,
228
            AttributeConsumingService::class,
229
            'All md:AttributeConsumingService endpoints must be an instance of AttributeConsumingService.'
230
        );
231
        $this->attributeConsumingService = $acs;
232
    }
233
234
235
    /**
236
     * Convert XML into a SPSSODescriptor
237
     *
238
     * @param \DOMElement $xml The XML element we should load
239
     *
240
     * @return self
241
     *
242
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException if the qualified name of the supplied element is wrong
243
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException if the supplied element is missing one of the mandatory attributes
244
     * @throws \SimpleSAML\XML\Exception\TooManyElementsException if too many child-elements of a type are specified
245
     */
246
    public static function fromXML(DOMElement $xml): object
247
    {
248
        Assert::same($xml->localName, 'SPSSODescriptor', InvalidDOMElementException::class);
249
        Assert::same($xml->namespaceURI, SPSSODescriptor::NS, InvalidDOMElementException::class);
250
251
        $protocols = self::getAttribute($xml, 'protocolSupportEnumeration');
252
        $validUntil = self::getAttribute($xml, 'validUntil', null);
253
        $orgs = Organization::getChildrenOfClass($xml);
254
        Assert::maxCount($orgs, 1, 'More than one Organization found in this descriptor', TooManyElementsException::class);
255
256
        $extensions = Extensions::getChildrenOfClass($xml);
257
        Assert::maxCount($extensions, 1, 'Only one md:Extensions element is allowed.', TooManyElementsException::class);
258
259
        $signature = Signature::getChildrenOfClass($xml);
260
        Assert::maxCount($signature, 1, 'Only one ds:Signature element is allowed.', TooManyElementsException::class);
261
262
        $spssod = new self(
263
            AssertionConsumerService::getChildrenOfClass($xml),
264
            preg_split('/[\s]+/', trim($protocols)),
265
            self::getBooleanAttribute($xml, 'AuthnRequestsSigned', null),
266
            self::getBooleanAttribute($xml, 'WantAssertionsSigned', null),
267
            AttributeConsumingService::getChildrenOfClass($xml),
268
            self::getAttribute($xml, 'ID', null),
269
            $validUntil !== null ? XMLUtils::xsDateTimeToTimestamp($validUntil) : null,
270
            self::getAttribute($xml, 'cacheDuration', null),
271
            !empty($extensions) ? $extensions[0] : null,
272
            self::getAttribute($xml, 'errorURL', null),
273
            KeyDescriptor::getChildrenOfClass($xml),
274
            !empty($orgs) ? $orgs[0] : null,
275
            ContactPerson::getChildrenOfClass($xml),
276
            ArtifactResolutionService::getChildrenOfClass($xml),
277
            SingleLogoutService::getChildrenOfClass($xml),
278
            ManageNameIDService::getChildrenOfClass($xml),
279
            NameIDFormat::getChildrenOfClass($xml)
280
        );
281
282
        if (!empty($signature)) {
283
            $spssod->setSignature($signature[0]);
284
        }
285
286
        $spssod->setXML($xml);
287
288
        return $spssod;
289
    }
290
291
292
    /**
293
     * Convert this descriptor to an unsigned XML document.
294
     * This method does not sign the resulting XML document.
295
     *
296
     * @param \DOMElement|null $parent
297
     * @return \DOMElement The root element of the DOM tree
298
     */
299
    protected function toUnsignedXML(DOMElement $parent = null): DOMElement
300
    {
301
        $e = parent::toUnsignedXML($parent);
302
303
        if (is_bool($this->authnRequestsSigned)) {
304
            $e->setAttribute('AuthnRequestsSigned', $this->authnRequestsSigned ? 'true' : 'false');
305
        }
306
307
        if (is_bool($this->wantAssertionsSigned)) {
308
            $e->setAttribute('WantAssertionsSigned', $this->wantAssertionsSigned ? 'true' : 'false');
309
        }
310
311
        foreach ($this->assertionConsumerService as $ep) {
312
            $ep->toXML($e);
313
        }
314
315
        foreach ($this->attributeConsumingService as $acs) {
316
            $acs->toXML($e);
317
        }
318
319
        return $e;
320
    }
321
}
322