AuthnRequest::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 42
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 18
nc 1
nop 19
dl 0
loc 42
rs 9.6666
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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