AuthnRequest::toUnsignedXML()   F
last analyzed

Complexity

Conditions 14
Paths 640

Size

Total Lines 51
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 28
nc 640
nop 1
dl 0
loc 51
rs 2.6
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\SAML2\XML\samlp;
6
7
use DateTimeImmutable;
8
use DOMElement;
9
use SimpleSAML\SAML2\Assert\Assert;
10
use SimpleSAML\SAML2\Exception\Protocol\RequestVersionTooHighException;
11
use SimpleSAML\SAML2\Exception\Protocol\RequestVersionTooLowException;
12
use SimpleSAML\SAML2\Exception\ProtocolViolationException;
13
use SimpleSAML\SAML2\XML\saml\Conditions;
14
use SimpleSAML\SAML2\XML\saml\Issuer;
15
use SimpleSAML\SAML2\XML\saml\Subject;
16
use SimpleSAML\XML\Exception\InvalidDOMElementException;
17
use SimpleSAML\XML\Exception\TooManyElementsException;
18
use SimpleSAML\XML\SchemaValidatableElementInterface;
19
use SimpleSAML\XML\SchemaValidatableElementTrait;
20
use SimpleSAML\XMLSecurity\XML\ds\Signature;
21
22
use function array_pop;
23
use function strval;
24
25
/**
26
 * Class for SAML 2 authentication request messages.
27
 *
28
 * @package simplesamlphp/saml2
29
 */
30
class AuthnRequest extends AbstractRequest implements SchemaValidatableElementInterface
31
{
32
    use SchemaValidatableElementTrait;
33
34
    /**
35
     * Constructor for SAML 2 AuthnRequest
36
     *
37
     * @param \DateTimeImmutable $issueInstant
38
     * @param \SimpleSAML\SAML2\XML\samlp\RequestedAuthnContext|null $requestedAuthnContext
39
     * @param \SimpleSAML\SAML2\XML\saml\Subject|null $subject
40
     * @param \SimpleSAML\SAML2\XML\samlp\NameIDPolicy|null $nameIdPolicy
41
     * @param \SimpleSAML\SAML2\XML\saml\Conditions|null $conditions
42
     * @param bool|null $forceAuthn
43
     * @param bool|null $isPassive
44
     * @param string|null $assertionConsumerServiceURL
45
     * @param int|null $assertionConsumerServiceIndex
46
     * @param string|null $protocolBinding
47
     * @param int|null $attributeConsumingServiceIndex
48
     * @param string|null $providerName
49
     * @param \SimpleSAML\SAML2\XML\saml\Issuer|null $issuer
50
     * @param string|null $id
51
     * @param string $version
52
     * @param string|null $destination
53
     * @param string|null $consent
54
     * @param \SimpleSAML\SAML2\XML\samlp\Extensions|null $extensions
55
     * @param \SimpleSAML\SAML2\XML\samlp\Scoping|null $scoping
56
     * @throws \Exception
57
     */
58
    final public function __construct(
59
        DateTimeImmutable $issueInstant,
60
        protected ?RequestedAuthnContext $requestedAuthnContext = null,
61
        protected ?Subject $subject = null,
62
        protected ?NameIDPolicy $nameIdPolicy = null,
63
        protected ?Conditions $conditions = null,
64
        protected ?bool $forceAuthn = null,
65
        protected ?bool $isPassive = null,
66
        protected ?string $assertionConsumerServiceURL = null,
67
        protected ?int $assertionConsumerServiceIndex = null,
68
        protected ?string $protocolBinding = null,
69
        protected ?int $attributeConsumingServiceIndex = null,
70
        protected ?string $providerName = null,
71
        ?Issuer $issuer = null,
72
        ?string $id = null,
73
        string $version = '2.0',
74
        ?string $destination = null,
75
        ?string $consent = null,
76
        ?Extensions $extensions = null,
77
        protected ?Scoping $scoping = null,
78
    ) {
79
        Assert::nullOrNotWhitespaceOnly($providerName);
80
        Assert::oneOf(
81
            null,
82
            [$assertionConsumerServiceURL, $assertionConsumerServiceIndex],
83
            'The AssertionConsumerServiceURL and AssertionConsumerServiceIndex are mutually exclusive;'
84
            . ' please specify one or the other.',
85
            ProtocolViolationException::class,
86
        );
87
        Assert::oneOf(
88
            null,
89
            [$protocolBinding, $assertionConsumerServiceIndex],
90
            'The ProtocolBinding and AssertionConsumerServiceIndex are mutually exclusive;'
91
            . ' please specify one or the other.',
92
            ProtocolViolationException::class,
93
        );
94
        Assert::nullOrValidURL($assertionConsumerServiceURL);
95
        Assert::nullOrValidURI($protocolBinding);
96
        Assert::nullOrRange($attributeConsumingServiceIndex, 0, 65535);
97
        Assert::nullOrRange($assertionConsumerServiceIndex, 0, 65535);
98
99
        parent::__construct($issuer, $id, $version, $issueInstant, $destination, $consent, $extensions);
100
    }
101
102
103
    /**
104
     * @return \SimpleSAML\SAML2\XML\saml\Subject|null
105
     */
106
    public function getSubject(): ?Subject
107
    {
108
        return $this->subject;
109
    }
110
111
112
    /**
113
     * @return \SimpleSAML\SAML2\XML\samlp\Scoping|null
114
     */
115
    public function getScoping(): ?Scoping
116
    {
117
        return $this->scoping;
118
    }
119
120
121
    /**
122
     * @return \SimpleSAML\SAML2\XML\saml\Conditions|null
123
     */
124
    public function getConditions(): ?Conditions
125
    {
126
        return $this->conditions;
127
    }
128
129
130
    /**
131
     * Retrieve the NameIdPolicy.
132
     *
133
     * @see \SimpleSAML\SAML2\AuthnRequest::setNameIdPolicy()
134
     * @return \SimpleSAML\SAML2\XML\samlp\NameIDPolicy|null The NameIdPolicy.
135
     */
136
    public function getNameIdPolicy(): ?NameIDPolicy
137
    {
138
        return $this->nameIdPolicy;
139
    }
140
141
142
    /**
143
     * Retrieve the value of the ForceAuthn attribute.
144
     *
145
     * @return bool|null The ForceAuthn attribute.
146
     */
147
    public function getForceAuthn(): ?bool
148
    {
149
        return $this->forceAuthn;
150
    }
151
152
153
    /**
154
     * Retrieve the value of the ProviderName attribute.
155
     *
156
     * @return string|null The ProviderName attribute.
157
     */
158
    public function getProviderName(): ?string
159
    {
160
        return $this->providerName;
161
    }
162
163
164
    /**
165
     * Retrieve the value of the IsPassive attribute.
166
     *
167
     * @return bool|null The IsPassive attribute.
168
     */
169
    public function getIsPassive(): ?bool
170
    {
171
        return $this->isPassive;
172
    }
173
174
175
    /**
176
     * Retrieve the value of the AssertionConsumerServiceURL attribute.
177
     *
178
     * @return string|null The AssertionConsumerServiceURL attribute.
179
     */
180
    public function getAssertionConsumerServiceURL(): ?string
181
    {
182
        return $this->assertionConsumerServiceURL;
183
    }
184
185
186
    /**
187
     * Retrieve the value of the ProtocolBinding attribute.
188
     *
189
     * @return string|null The ProtocolBinding attribute.
190
     */
191
    public function getProtocolBinding(): ?string
192
    {
193
        return $this->protocolBinding;
194
    }
195
196
197
    /**
198
     * Retrieve the value of the AttributeConsumingServiceIndex attribute.
199
     *
200
     * @return int|null The AttributeConsumingServiceIndex attribute.
201
     */
202
    public function getAttributeConsumingServiceIndex(): ?int
203
    {
204
        return $this->attributeConsumingServiceIndex;
205
    }
206
207
208
    /**
209
     * Retrieve the value of the AssertionConsumerServiceIndex attribute.
210
     *
211
     * @return int|null The AssertionConsumerServiceIndex attribute.
212
     */
213
    public function getAssertionConsumerServiceIndex(): ?int
214
    {
215
        return $this->assertionConsumerServiceIndex;
216
    }
217
218
219
    /**
220
     * Retrieve the RequestedAuthnContext.
221
     *
222
     * @return \SimpleSAML\SAML2\XML\samlp\RequestedAuthnContext|null The RequestedAuthnContext.
223
     */
224
    public function getRequestedAuthnContext(): ?RequestedAuthnContext
225
    {
226
        return $this->requestedAuthnContext;
227
    }
228
229
230
    /**
231
     * Convert XML into an AuthnRequest
232
     *
233
     * @param \DOMElement $xml The XML element we should load
234
     * @return static
235
     *
236
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException
237
     *   if the qualified name of the supplied element is wrong
238
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException
239
     *   if the supplied element is missing one of the mandatory attributes
240
     * @throws \SimpleSAML\XML\Exception\TooManyElementsException
241
     *   if too many child-elements of a type are specified
242
     */
243
    public static function fromXML(DOMElement $xml): static
244
    {
245
        Assert::same($xml->localName, 'AuthnRequest', InvalidDOMElementException::class);
246
        Assert::same($xml->namespaceURI, AuthnRequest::NS, InvalidDOMElementException::class);
247
248
        $version = self::getAttribute($xml, 'Version');
249
        Assert::true(version_compare('2.0', $version, '<='), RequestVersionTooLowException::class);
250
        Assert::true(version_compare('2.0', $version, '>='), RequestVersionTooHighException::class);
251
252
        $id = self::getAttribute($xml, 'ID');
253
        Assert::validNCName($id); // Covers the empty string
254
255
        $issueInstant = self::getAttribute($xml, 'IssueInstant');
256
        // Strip sub-seconds - See paragraph 1.3.3 of SAML core specifications
257
        $issueInstant = preg_replace('/([.][0-9]+Z)$/', 'Z', $issueInstant, 1);
258
259
        Assert::validDateTime($issueInstant, ProtocolViolationException::class);
260
        $issueInstant = new DateTimeImmutable($issueInstant);
261
262
        $attributeConsumingServiceIndex = self::getOptionalIntegerAttribute(
263
            $xml,
264
            'AttributeConsumingServiceIndex',
265
            null,
266
        );
267
        $assertionConsumerServiceIndex = self::getOptionalIntegerAttribute(
268
            $xml,
269
            'AssertionConsumerServiceIndex',
270
            null,
271
        );
272
273
        $conditions = Conditions::getChildrenOfClass($xml);
274
        Assert::maxCount(
275
            $conditions,
276
            1,
277
            'Only one <saml:Conditions> element is allowed.',
278
            TooManyElementsException::class,
279
        );
280
281
        $nameIdPolicy = NameIDPolicy::getChildrenOfClass($xml);
282
        Assert::maxCount(
283
            $nameIdPolicy,
284
            1,
285
            'Only one <samlp:NameIDPolicy> element is allowed.',
286
            TooManyElementsException::class,
287
        );
288
289
        $subject = Subject::getChildrenOfClass($xml);
290
        Assert::maxCount($subject, 1, 'Only one <saml:Subject> element is allowed.', TooManyElementsException::class);
291
292
        $issuer = Issuer::getChildrenOfClass($xml);
293
        Assert::maxCount($issuer, 1, 'Only one <saml:Issuer> element is allowed.', TooManyElementsException::class);
294
295
        $requestedAuthnContext = RequestedAuthnContext::getChildrenOfClass($xml);
296
        Assert::maxCount(
297
            $requestedAuthnContext,
298
            1,
299
            'Only one <samlp:RequestedAuthnContext> element is allowed.',
300
            TooManyElementsException::class,
301
        );
302
303
        $extensions = Extensions::getChildrenOfClass($xml);
304
        Assert::maxCount(
305
            $extensions,
306
            1,
307
            'Only one <samlp:Extensions> element is allowed.',
308
            TooManyElementsException::class,
309
        );
310
311
        $signature = Signature::getChildrenOfClass($xml);
312
        Assert::maxCount(
313
            $signature,
314
            1,
315
            'Only one <ds:Signature> element is allowed.',
316
            TooManyElementsException::class,
317
        );
318
319
        $scoping = Scoping::getChildrenOfClass($xml);
320
        Assert::maxCount($scoping, 1, 'Only one <samlp:Scoping> element is allowed.', TooManyElementsException::class);
321
322
        $request = new static(
323
            $issueInstant,
324
            array_pop($requestedAuthnContext),
325
            array_pop($subject),
326
            array_pop($nameIdPolicy),
327
            array_pop($conditions),
328
            self::getOptionalBooleanAttribute($xml, 'ForceAuthn', null),
329
            self::getOptionalBooleanAttribute($xml, 'IsPassive', null),
330
            self::getOptionalAttribute($xml, 'AssertionConsumerServiceURL', null),
331
            $assertionConsumerServiceIndex,
332
            self::getOptionalAttribute($xml, 'ProtocolBinding', null),
333
            $attributeConsumingServiceIndex,
334
            self::getOptionalAttribute($xml, 'ProviderName', null),
335
            array_pop($issuer),
336
            $id,
337
            $version,
338
            self::getOptionalAttribute($xml, 'Destination', null),
339
            self::getOptionalAttribute($xml, 'Consent', null),
340
            array_pop($extensions),
341
            array_pop($scoping),
342
        );
343
344
        if (!empty($signature)) {
345
            $request->setSignature($signature[0]);
346
            $request->messageContainedSignatureUponConstruction = true;
347
            $request->setXML($xml);
348
        }
349
350
        return $request;
351
    }
352
353
354
    /**
355
     * Convert this message to an unsigned XML document.
356
     * This method does not sign the resulting XML document.
357
     *
358
     * @return \DOMElement The root element of the DOM tree
359
     */
360
    protected function toUnsignedXML(?DOMElement $parent = null): DOMElement
361
    {
362
        $e = parent::toUnsignedXML($parent);
363
364
        if ($this->getForceAuthn() === true) {
365
            $e->setAttribute('ForceAuthn', 'true');
366
        }
367
368
        if ($this->getProviderName() !== null) {
369
            $e->setAttribute('ProviderName', $this->getProviderName());
370
        }
371
372
        if ($this->getIsPassive() === true) {
373
            $e->setAttribute('IsPassive', 'true');
374
        }
375
376
        if ($this->getAssertionConsumerServiceIndex() !== null) {
377
            $e->setAttribute('AssertionConsumerServiceIndex', strval($this->getAssertionConsumerServiceIndex()));
378
        } else {
379
            if ($this->getAssertionConsumerServiceURL() !== null) {
380
                $e->setAttribute('AssertionConsumerServiceURL', $this->getAssertionConsumerServiceURL());
381
            }
382
            if ($this->getProtocolBinding() !== null) {
383
                $e->setAttribute('ProtocolBinding', $this->getProtocolBinding());
384
            }
385
        }
386
387
        if ($this->getAttributeConsumingServiceIndex() !== null) {
388
            $e->setAttribute('AttributeConsumingServiceIndex', strval($this->getAttributeConsumingServiceIndex()));
389
        }
390
391
        $this->getSubject()?->toXML($e);
392
393
        $nameIdPolicy = $this->getNameIdPolicy();
394
        if ($nameIdPolicy !== null && !$nameIdPolicy->isEmptyElement()) {
395
            $nameIdPolicy->toXML($e);
396
        }
397
398
        $conditions = $this->getConditions();
399
        if ($conditions !== null && !$conditions->isEmptyElement()) {
400
            $conditions->toXML($e);
401
        }
402
403
        $this->getRequestedAuthnContext()?->toXML($e);
404
405
        $scoping = $this->getScoping();
406
        if ($scoping !== null && !$scoping->isEmptyElement()) {
407
            $scoping->toXML($e);
408
        }
409
410
        return $e;
411
    }
412
}
413