AuthnRequest   A
last analyzed

Complexity

Total Complexity 29

Size/Duplication

Total Lines 382
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 29
eloc 134
dl 0
loc 382
rs 10
c 0
b 0
f 0

15 Methods

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