Passed
Push — master ( d3e6a5...90893b )
by Tim
02:27
created

AuthnRequest::getAssertionConsumerServiceIndex()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\SAML2\XML\samlp;
6
7
use DOMElement;
8
use SimpleSAML\Assert\Assert;
9
use SimpleSAML\SAML2\Exception\InvalidArgumentException;
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\SAML2\XML\saml\SubjectConfirmation;
17
use SimpleSAML\XML\Exception\InvalidDOMElementException;
18
use SimpleSAML\XML\Exception\TooManyElementsException;
19
use SimpleSAML\XML\Utils as XMLUtils;
20
use SimpleSAML\XMLSecurity\XML\ds\Signature;
21
22
use function array_pop;
23
use function filter_var;
24
use function is_null;
25
use function strval;
26
27
/**
28
 * Class for SAML 2 authentication request messages.
29
 *
30
 * @package simplesamlphp/saml2
31
 */
32
class AuthnRequest extends AbstractRequest
33
{
34
    /**
35
     * @var \SimpleSAML\SAML2\XML\saml\Subject|null
36
     */
37
    protected ?Subject $subject = null;
38
39
    /**
40
     * @var \SimpleSAML\SAML2\XML\samlp\Scoping|null
41
     */
42
    protected ?Scoping $scoping = null;
43
44
    /**
45
     * The options for what type of name identifier should be returned.
46
     *
47
     * @var \SimpleSAML\SAML2\XML\samlp\NameIDPolicy|null
48
     */
49
    protected ?NameIDPolicy $nameIdPolicy = null;
50
51
    /**
52
     * Whether the Identity Provider must authenticate the user again.
53
     *
54
     * @var bool|null
55
     */
56
    protected ?bool $forceAuthn = false;
57
58
    /**
59
     * Optional ProviderID attribute
60
     *
61
     * @var string|null
62
     */
63
    protected ?string $ProviderName = null;
64
65
    /**
66
     * Set to true if this request is passive.
67
     *
68
     * @var bool|null
69
     */
70
    protected ?bool $isPassive = false;
71
72
    /**
73
     * The URL of the assertion consumer service where the response should be delivered.
74
     *
75
     * @var string|null
76
     */
77
    protected ?string $assertionConsumerServiceURL;
78
79
    /**
80
     * What binding should be used when sending the response.
81
     *
82
     * @var string|null
83
     */
84
    protected ?string $protocolBinding;
85
86
    /**
87
     * The index of the AttributeConsumingService.
88
     *
89
     * @var int|null
90
     */
91
    protected ?int $attributeConsumingServiceIndex;
92
93
    /**
94
     * The index of the AssertionConsumerService.
95
     *
96
     * @var int|null
97
     */
98
    protected ?int $assertionConsumerServiceIndex = null;
99
100
    /**
101
     * What authentication context was requested.
102
     *
103
     * @var \SimpleSAML\SAML2\XML\samlp\RequestedAuthnContext|null
104
     */
105
    protected ?RequestedAuthnContext $requestedAuthnContext;
106
107
    /**
108
     * @var \SimpleSAML\SAML2\XML\saml\Conditions|null
109
     */
110
    protected ?Conditions $conditions = null;
111
112
113
    /**
114
     * Constructor for SAML 2 AuthnRequest
115
     *
116
     * @param \SimpleSAML\SAML2\XML\samlp\RequestedAuthnContext $requestedAuthnContext
117
     * @param \SimpleSAML\SAML2\XML\saml\Subject $subject
118
     * @param \SimpleSAML\SAML2\XML\samlp\NameIDPolicy $nameIdPolicy
119
     * @param \SimpleSAML\SAML2\XML\saml\Conditions $conditions
120
     * @param bool $forceAuthn
121
     * @param bool $isPassive
122
     * @param string|null $assertionConsumerServiceUrl
123
     * @param int|null $assertionConsumerServiceIndex
124
     * @param string|null $protocolBinding
125
     * @param int|null $attributeConsumingServiceIndex
126
     * @param string|null $providerName
127
     * @param \SimpleSAML\SAML2\XML\saml\Issuer|null $issuer
128
     * @param string|null $id
129
     * @param int|null $issueInstant
130
     * @param string|null $destination
131
     * @param string|null $consent
132
     * @param \SimpleSAML\SAML2\XML\samlp\Extensions|null $extensions
133
     * @param \SimpleSAML\SAML2\XML\samlp\Scoping|null $scoping
134
     * @throws \Exception
135
     */
136
    public function __construct(
137
        ?RequestedAuthnContext $requestedAuthnContext = null,
138
        ?Subject $subject = null,
139
        ?NameIDPolicy $nameIdPolicy = null,
140
        Conditions $conditions = null,
141
        ?bool $forceAuthn = null,
142
        ?bool $isPassive = null,
143
        ?string $assertionConsumerServiceUrl = null,
144
        ?int $assertionConsumerServiceIndex = null,
145
        ?string $protocolBinding = null,
146
        ?int $attributeConsumingServiceIndex = null,
147
        ?string $providerName = null,
148
        ?Issuer $issuer = null,
149
        ?string $id = null,
150
        ?int $issueInstant = null,
151
        ?string $destination = null,
152
        ?string $consent = null,
153
        ?Extensions $extensions = null,
154
        ?Scoping $scoping = null
155
    ) {
156
        parent::__construct($issuer, $id, $issueInstant, $destination, $consent, $extensions);
157
158
        $this->setRequestedAuthnContext($requestedAuthnContext);
159
        $this->setSubject($subject);
160
        $this->setNameIdPolicy($nameIdPolicy);
161
        $this->setConditions($conditions);
162
163
        $this->setForceAuthn($forceAuthn);
164
        $this->setIsPassive($isPassive);
165
        $this->setAssertionConsumerServiceUrl($assertionConsumerServiceUrl);
166
        $this->setAssertionConsumerServiceIndex($assertionConsumerServiceIndex);
167
        $this->setProtocolBinding($protocolBinding);
168
        $this->setAttributeConsumingServiceIndex($attributeConsumingServiceIndex);
169
        $this->setProviderName($providerName);
170
        $this->setScoping($scoping);
171
    }
172
173
174
    /**
175
     * @param \SimpleSAML\SAML2\XML\saml\Subject|null $subject
176
     */
177
    private function setSubject(?Subject $subject): void
178
    {
179
        $this->subject = $subject;
180
    }
181
182
183
    /**
184
     * @return \SimpleSAML\SAML2\XML\saml\Subject|null
185
     */
186
    public function getSubject(): ?Subject
187
    {
188
        return $this->subject;
189
    }
190
191
192
    /**
193
     * @param \SimpleSAML\SAML2\XML\samlp\Scoping|null $scoping
194
     */
195
    private function setScoping(?Scoping $scoping): void
196
    {
197
        $this->scoping = $scoping;
198
    }
199
200
201
    /**
202
     * @return \SimpleSAML\SAML2\XML\samlp\Scoping|null
203
     */
204
    public function getScoping(): ?Scoping
205
    {
206
        return $this->scoping;
207
    }
208
209
210
    /**
211
     * @param \SimpleSAML\SAML2\XML\saml\Conditions|null $conditions
212
     */
213
    private function setConditions(?Conditions $conditions): void
214
    {
215
        $this->conditions = $conditions;
216
    }
217
218
219
    /**
220
     * @return \SimpleSAML\SAML2\XML\saml\Conditions|null
221
     */
222
    public function getConditions(): ?Conditions
223
    {
224
        return $this->conditions;
225
    }
226
227
228
    /**
229
     * Retrieve the NameIdPolicy.
230
     *
231
     * @see \SimpleSAML\SAML2\AuthnRequest::setNameIdPolicy()
232
     * @return \SimpleSAML\SAML2\XML\samlp\NameIDPolicy|null The NameIdPolicy.
233
     */
234
    public function getNameIdPolicy(): ?NameIDPolicy
235
    {
236
        return $this->nameIdPolicy;
237
    }
238
239
240
    /**
241
     * Set the NameIDPolicy.
242
     *
243
     * @param \SimpleSAML\SAML2\XML\samlp\NameIDPolicy|null $nameIdPolicy The NameIDPolicy.
244
     */
245
    private function setNameIdPolicy(?NameIDPolicy $nameIdPolicy): void
246
    {
247
        $this->nameIdPolicy = $nameIdPolicy;
248
    }
249
250
251
    /**
252
     * Retrieve the value of the ForceAuthn attribute.
253
     *
254
     * @return bool|null The ForceAuthn attribute.
255
     */
256
    public function getForceAuthn(): ?bool
257
    {
258
        return $this->forceAuthn;
259
    }
260
261
262
    /**
263
     * Set the value of the ForceAuthn attribute.
264
     *
265
     * @param bool $forceAuthn The ForceAuthn attribute.
266
     */
267
    private function setForceAuthn(?bool $forceAuthn): void
268
    {
269
        $this->forceAuthn = $forceAuthn;
270
    }
271
272
273
    /**
274
     * Retrieve the value of the ProviderName attribute.
275
     *
276
     * @return string|null The ProviderName attribute.
277
     */
278
    public function getProviderName(): ?string
279
    {
280
        return $this->ProviderName;
281
    }
282
283
284
    /**
285
     * Set the value of the ProviderName attribute.
286
     *
287
     * @param string|null $ProviderName The ProviderName attribute.
288
     */
289
    private function setProviderName(?string $ProviderName): void
290
    {
291
        Assert::nullOrNotWhitespaceOnly($ProviderName);
292
        $this->ProviderName = $ProviderName;
293
    }
294
295
296
    /**
297
     * Retrieve the value of the IsPassive attribute.
298
     *
299
     * @return bool|null The IsPassive attribute.
300
     */
301
    public function getIsPassive(): ?bool
302
    {
303
        return $this->isPassive;
304
    }
305
306
307
    /**
308
     * Set the value of the IsPassive attribute.
309
     *
310
     * @param bool|null $isPassive The IsPassive attribute.
311
     */
312
    private function setIsPassive(?bool $isPassive): void
313
    {
314
        $this->isPassive = $isPassive;
315
    }
316
317
318
    /**
319
     * Retrieve the value of the AssertionConsumerServiceURL attribute.
320
     *
321
     * @return string|null The AssertionConsumerServiceURL attribute.
322
     */
323
    public function getAssertionConsumerServiceURL(): ?string
324
    {
325
        return $this->assertionConsumerServiceURL;
326
    }
327
328
329
    /**
330
     * Set the value of the AssertionConsumerServiceURL attribute.
331
     *
332
     * @param string|null $assertionConsumerServiceURL The AssertionConsumerServiceURL attribute.
333
     */
334
    private function setAssertionConsumerServiceURL(?string $assertionConsumerServiceURL): void
335
    {
336
        Assert::nullOrValidURL($assertionConsumerServiceURL);
337
        $this->assertionConsumerServiceURL = $assertionConsumerServiceURL;
338
    }
339
340
341
    /**
342
     * Retrieve the value of the ProtocolBinding attribute.
343
     *
344
     * @return string|null The ProtocolBinding attribute.
345
     */
346
    public function getProtocolBinding(): ?string
347
    {
348
        return $this->protocolBinding;
349
    }
350
351
352
    /**
353
     * Set the value of the ProtocolBinding attribute.
354
     *
355
     * @param string|null $protocolBinding The ProtocolBinding attribute.
356
     */
357
    private function setProtocolBinding(?string $protocolBinding): void
358
    {
359
        Assert::nullOrValidURI($protocolBinding); // Covers the empty string
360
361
        $this->protocolBinding = $protocolBinding;
362
    }
363
364
365
    /**
366
     * Retrieve the value of the AttributeConsumingServiceIndex attribute.
367
     *
368
     * @return int|null The AttributeConsumingServiceIndex attribute.
369
     */
370
    public function getAttributeConsumingServiceIndex(): ?int
371
    {
372
        return $this->attributeConsumingServiceIndex;
373
    }
374
375
376
    /**
377
     * Set the value of the AttributeConsumingServiceIndex attribute.
378
     *
379
     * @param int|null $attributeConsumingServiceIndex The AttributeConsumingServiceIndex attribute.
380
     */
381
    private function setAttributeConsumingServiceIndex(?int $attributeConsumingServiceIndex): void
382
    {
383
        Assert::nullOrRange($attributeConsumingServiceIndex, 0, 65535);
384
        $this->attributeConsumingServiceIndex = $attributeConsumingServiceIndex;
385
    }
386
387
388
    /**
389
     * Retrieve the value of the AssertionConsumerServiceIndex attribute.
390
     *
391
     * @return int|null The AssertionConsumerServiceIndex attribute.
392
     */
393
    public function getAssertionConsumerServiceIndex(): ?int
394
    {
395
        return $this->assertionConsumerServiceIndex;
396
    }
397
398
399
    /**
400
     * Set the value of the AssertionConsumerServiceIndex attribute.
401
     *
402
     * @param int|null $assertionConsumerServiceIndex The AssertionConsumerServiceIndex attribute.
403
     */
404
    private function setAssertionConsumerServiceIndex(?int $assertionConsumerServiceIndex): void
405
    {
406
        Assert::nullOrRange($assertionConsumerServiceIndex, 0, 65535);
407
        $this->assertionConsumerServiceIndex = $assertionConsumerServiceIndex;
408
    }
409
410
411
    /**
412
     * Retrieve the RequestedAuthnContext.
413
     *
414
     * @return \SimpleSAML\SAML2\XML\samlp\RequestedAuthnContext|null The RequestedAuthnContext.
415
     */
416
    public function getRequestedAuthnContext(): ?RequestedAuthnContext
417
    {
418
        return $this->requestedAuthnContext;
419
    }
420
421
422
    /**
423
     * Set the RequestedAuthnContext.
424
     *
425
     * @param \SimpleSAML\SAML2\XML\samlp\RequestedAuthnContext|null $requestedAuthnContext The RequestedAuthnContext.
426
     */
427
    private function setRequestedAuthnContext(RequestedAuthnContext $requestedAuthnContext = null): void
428
    {
429
        $this->requestedAuthnContext = $requestedAuthnContext;
430
    }
431
432
433
    /**
434
     * Convert XML into an AuthnRequest
435
     *
436
     * @param \DOMElement $xml The XML element we should load
437
     * @return \SimpleSAML\SAML2\XML\samlp\AuthnRequest
438
     *
439
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException if the qualified name of the supplied element is wrong
440
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException if the supplied element is missing one of the mandatory attributes
441
     * @throws \SimpleSAML\XML\Exception\TooManyElementsException if too many child-elements of a type are specified
442
     */
443
    public static function fromXML(DOMElement $xml): static
444
    {
445
        Assert::same($xml->localName, 'AuthnRequest', InvalidDOMElementException::class);
446
        Assert::same($xml->namespaceURI, AuthnRequest::NS, InvalidDOMElementException::class);
447
448
        Assert::true(version_compare('2.0', self::getAttribute($xml, 'Version'), '<='), RequestVersionTooLowException::class);
449
        Assert::true(version_compare('2.0', self::getAttribute($xml, 'Version'), '>='), RequestVersionTooHighException::class);
450
451
        $issueInstant = self::getAttribute($xml, 'IssueInstant');
452
        // Strip sub-seconds - See paragraph 1.3.3 of SAML core specifications
453
        $issueInstant = preg_replace('/([.][0-9]+Z)$/', 'Z', $issueInstant, 1);
454
455
        Assert::validDateTimeZulu($issueInstant, ProtocolViolationException::class);
456
        $issueInstant = XMLUtils::xsDateTimeToTimestamp($issueInstant);
457
458
        $attributeConsumingServiceIndex = self::getIntegerAttribute($xml, 'AttributeConsumingServiceIndex', null);
459
        $assertionConsumerServiceIndex = self::getIntegerAttribute($xml, 'AssertionConsumerServiceIndex', null);
460
461
        $conditions = Conditions::getChildrenOfClass($xml);
462
        Assert::maxCount($conditions, 1, 'Only one <saml:Conditions> element is allowed.', TooManyElementsException::class);
463
464
        $nameIdPolicy = NameIDPolicy::getChildrenOfClass($xml);
465
        Assert::maxCount($nameIdPolicy, 1, 'Only one <samlp:NameIDPolicy> element is allowed.', TooManyElementsException::class);
466
467
        $subject = Subject::getChildrenOfClass($xml);
468
        Assert::maxCount($subject, 1, 'Only one <saml:Subject> element is allowed.', TooManyElementsException::class);
469
470
        $issuer = Issuer::getChildrenOfClass($xml);
471
        Assert::maxCount($issuer, 1, 'Only one <saml:Issuer> element is allowed.', TooManyElementsException::class);
472
473
        $requestedAuthnContext = RequestedAuthnContext::getChildrenOfClass($xml);
474
        Assert::maxCount(
475
            $requestedAuthnContext,
476
            1,
477
            'Only one <samlp:RequestedAuthnContext> element is allowed.',
478
            TooManyElementsException::class
479
        );
480
481
        $extensions = Extensions::getChildrenOfClass($xml);
482
        Assert::maxCount($extensions, 1, 'Only one <samlp:Extensions> element is allowed.', TooManyElementsException::class);
483
484
        $signature = Signature::getChildrenOfClass($xml);
485
        Assert::maxCount($signature, 1, 'Only one <ds:Signature> element is allowed.', TooManyElementsException::class);
486
487
        $scoping = Scoping::getChildrenOfClass($xml);
488
        Assert::maxCount($scoping, 1, 'Only one <samlp:Scoping> element is allowed.', TooManyElementsException::class);
489
490
        $request = new static(
491
            array_pop($requestedAuthnContext),
492
            array_pop($subject),
493
            array_pop($nameIdPolicy),
494
            array_pop($conditions),
495
            self::getBooleanAttribute($xml, 'ForceAuthn', null),
496
            self::getBooleanAttribute($xml, 'IsPassive', null),
497
            self::getAttribute($xml, 'AssertionConsumerServiceURL', null),
498
            $assertionConsumerServiceIndex,
499
            self::getAttribute($xml, 'ProtocolBinding', null),
500
            $attributeConsumingServiceIndex,
501
            self::getAttribute($xml, 'ProviderName', null),
502
            array_pop($issuer),
503
            self::getAttribute($xml, 'ID'),
504
            $issueInstant,
505
            self::getAttribute($xml, 'Destination', null),
506
            self::getAttribute($xml, 'Consent', null),
507
            array_pop($extensions),
508
            array_pop($scoping)
509
        );
510
511
        if (!empty($signature)) {
512
            $request->setSignature($signature[0]);
513
            $request->messageContainedSignatureUponConstruction = true;
514
            $request->setXML($xml);
515
        }
516
517
        return $request;
518
    }
519
520
521
    /**
522
     * Convert this message to an unsigned XML document.
523
     * This method does not sign the resulting XML document.
524
     *
525
     * @return \DOMElement The root element of the DOM tree
526
     */
527
    protected function toUnsignedXML(?DOMElement $parent = null): DOMElement
528
    {
529
        $e = parent::toUnsignedXML($parent);
530
531
        if ($this->getForceAuthn() === true) {
532
            $e->setAttribute('ForceAuthn', 'true');
533
        }
534
535
        if (!empty($this->getProviderName())) {
536
            $e->setAttribute('ProviderName', $this->getProviderName());
537
        }
538
539
        if ($this->getIsPassive() === true) {
540
            $e->setAttribute('IsPassive', 'true');
541
        }
542
543
        if ($this->getAssertionConsumerServiceIndex() !== null) {
544
            $e->setAttribute('AssertionConsumerServiceIndex', strval($this->getAssertionConsumerServiceIndex()));
545
        } else {
546
            if ($this->getAssertionConsumerServiceURL() !== null) {
547
                $e->setAttribute('AssertionConsumerServiceURL', $this->getAssertionConsumerServiceURL());
548
            }
549
            if ($this->getProtocolBinding() !== null) {
550
                $e->setAttribute('ProtocolBinding', $this->getProtocolBinding());
551
            }
552
        }
553
554
        if ($this->getAttributeConsumingServiceIndex() !== null) {
555
            $e->setAttribute('AttributeConsumingServiceIndex', strval($this->getAttributeConsumingServiceIndex()));
556
        }
557
558
        $this->getSubject()?->toXML($e);
559
560
        if ($this->getNameIdPolicy() !== null && !$this->getNameIdPolicy()->isEmptyElement()) {
561
            $this->getNameIdPolicy()->toXML($e);
562
        }
563
564
        if ($this->getConditions() !== null && !$this->getConditions()->isEmptyElement()) {
565
            $this->getConditions()->toXML($e);
566
        }
567
568
        $this->getRequestedAuthnContext()?->toXML($e);
569
570
        if ($this->getScoping() !== null && !$this->getScoping()->isEmptyElement()) {
571
            $this->getScoping()->toXML($e);
572
        }
573
574
        return $e;
575
    }
576
}
577