Passed
Push — master ( 7a552f...3fe0a5 )
by Tim
02:53
created

AbstractMessage::addValidator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 5
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 DOMDocument;
8
use DOMElement;
9
use Exception;
10
use SimpleSAML\Assert\Assert;
11
use SimpleSAML\SAML2\Constants as C;
12
use SimpleSAML\SAML2\Utilities\Temporal;
13
use SimpleSAML\SAML2\Utils;
14
use SimpleSAML\SAML2\Utils\XPath;
15
use SimpleSAML\SAML2\XML\ExtendableElementTrait;
16
use SimpleSAML\SAML2\XML\saml\Issuer;
17
use SimpleSAML\XMLSecurity\Exception\NoSignatureFoundException;
18
use SimpleSAML\XMLSecurity\Key\PublicKey;
19
use SimpleSAML\XMLSecurity\XML\SignableElementInterface;
20
use SimpleSAML\XMLSecurity\XML\SignableElementTrait;
21
use SimpleSAML\XMLSecurity\XML\SignedElementInterface;
22
use SimpleSAML\XMLSecurity\XML\SignedElementTrait;
23
24
use function array_pop;
25
use function gmdate;
26
27
/**
28
 * Base class for all SAML 2 messages.
29
 *
30
 * Implements what is common between the samlp:RequestAbstractType and
31
 * samlp:StatusResponseType element types.
32
 *
33
 * @package simplesamlphp/saml2
34
 */
35
abstract class AbstractMessage extends AbstractSamlpElement implements SignableElementInterface, SignedElementInterface
36
{
37
    use ExtendableElementTrait;
38
    use SignableElementTrait;
39
    use SignedElementTrait;
40
41
    /**
42
     * The identifier of this message.
43
     *
44
     * @var string
45
     */
46
    protected string $id;
47
48
    /**
49
     * The version of this message.
50
     *
51
     * @var string
52
     */
53
    protected string $version = '2.0';
54
55
    /**
56
     * The issue timestamp of this message, as an UNIX timestamp.
57
     *
58
     * @var int
59
     */
60
    protected int $issueInstant;
61
62
    /**
63
     * The destination URL of this message if it is known.
64
     *
65
     * @var string|null
66
     */
67
    protected ?string $destination = null;
68
69
    /**
70
     * The destination URL of this message if it is known.
71
     *
72
     * @var string|null
73
     */
74
    protected ?string $consent;
75
76
    /**
77
     * The entity id of the issuer of this message, or null if unknown.
78
     *
79
     * @var \SimpleSAML\SAML2\XML\saml\Issuer|null
80
     */
81
    protected ?Issuer $issuer = null;
82
83
    /**
84
     * The RelayState associated with this message.
85
     *
86
     * @var string|null
87
     */
88
    protected ?string $relayState = null;
89
90
    /**
91
     * The \DOMDocument we are currently building.
92
     *
93
     * This variable is used while generating XML from this message. It holds the
94
     * \DOMDocument of the XML we are generating.
95
     *
96
     * @var \DOMDocument|null
97
     */
98
    protected ?DOMDocument $document = null;
99
100
    /** @var bool */
101
    protected bool $messageContainedSignatureUponConstruction = false;
102
103
104
    /**
105
     * Initialize a message.
106
     *
107
     * @param \SimpleSAML\SAML2\XML\saml\Issuer|null $issuer
108
     * @param string|null $id
109
     * @param int|null $issueInstant
110
     * @param string|null $destination
111
     * @param string|null $consent
112
     * @param \SimpleSAML\SAML2\XML\samlp\Extensions $extensions
113
     * @param string|null $relayState
114
     *
115
     * @throws \Exception
116
     */
117
    protected function __construct(
118
        ?Issuer $issuer = null,
119
        ?string $id = null,
120
        ?int $issueInstant = null,
121
        ?string $destination = null,
122
        ?string $consent = null,
123
        ?Extensions $extensions = null,
124
        ?string $relayState = null
125
    ) {
126
        $this->setIssuer($issuer);
127
        $this->setId($id);
128
        $this->setIssueInstant($issueInstant);
129
        $this->setDestination($destination);
130
        $this->setConsent($consent);
131
        $this->setExtensions($extensions);
132
        $this->setRelayState($relayState);
133
    }
134
135
136
    /**
137
     * Retrieve the identifier of this message.
138
     *
139
     * @return string The identifier of this message
140
     */
141
    public function getId(): string
142
    {
143
        return $this->id;
144
    }
145
146
147
    /**
148
     * Set the identifier of this message.
149
     *
150
     * @param string|null $id The new identifier of this message
151
     */
152
    private function setId(?string $id): void
153
    {
154
        Assert::nullOrNotWhitespaceOnly($id);
155
156
        if ($id === null) {
157
            $id = Utils::getContainer()->generateId();
158
        }
159
160
        $this->id = $id;
161
    }
162
163
164
    /**
165
     * Retrieve the version of this message.
166
     *
167
     * @return string The version of this message
168
     */
169
    public function getVersion(): string
170
    {
171
        return $this->version;
172
    }
173
174
175
    /**
176
     * Retrieve the issue timestamp of this message.
177
     *
178
     * @return int The issue timestamp of this message, as an UNIX timestamp
179
     */
180
    public function getIssueInstant(): int
181
    {
182
        return $this->issueInstant;
183
    }
184
185
186
    /**
187
     * Set the issue timestamp of this message.
188
     *
189
     * @param int|null $issueInstant The new issue timestamp of this message, as an UNIX timestamp
190
     */
191
    private function setIssueInstant(?int $issueInstant): void
192
    {
193
        if ($issueInstant === null) {
194
            $issueInstant = Temporal::getTime();
195
        }
196
197
        $this->issueInstant = $issueInstant;
198
    }
199
200
201
    /**
202
     * Retrieve the destination of this message.
203
     *
204
     * @return string|null The destination of this message, or NULL if no destination is given
205
     */
206
    public function getDestination(): ?string
207
    {
208
        return $this->destination;
209
    }
210
211
212
    /**
213
     * Set the destination of this message.
214
     *
215
     * @param string|null $destination The new destination of this message
216
     */
217
    private function setDestination(string $destination = null): void
218
    {
219
        Assert::nullOrValidURI($destination); // Covers the empty string
220
        $this->destination = $destination;
221
    }
222
223
224
    /**
225
     * Get the given consent for this message.
226
     * Most likely (though not required) a value of urn:oasis:names:tc:SAML:2.0:consent.
227
     *
228
     * @see \SimpleSAML\SAML2\Constants
229
     * @return string|null Consent
230
     */
231
    public function getConsent(): ?string
232
    {
233
        return $this->consent;
234
    }
235
236
237
    /**
238
     * Set the given consent for this message.
239
     * Most likely (though not required) a value of urn:oasis:names:tc:SAML:2.0:consent.
240
     *
241
     * @see \SimpleSAML\SAML2\Constants
242
     * @param string|null $consent
243
     */
244
    private function setConsent(?string $consent): void
245
    {
246
        Assert::nullOrValidURI($consent); // Covers the empty string
247
        $this->consent = $consent;
248
    }
249
250
251
    /**
252
     * Retrieve the issuer if this message.
253
     *
254
     * @return \SimpleSAML\SAML2\XML\saml\Issuer|null The issuer of this message, or NULL if no issuer is given
255
     */
256
    public function getIssuer(): ?Issuer
257
    {
258
        return $this->issuer;
259
    }
260
261
262
    /**
263
     * Set the issuer of this message.
264
     *
265
     * @param \SimpleSAML\SAML2\XML\saml\Issuer|null $issuer The new issuer of this message
266
     */
267
    private function setIssuer(Issuer $issuer = null): void
268
    {
269
        $this->issuer = $issuer;
270
    }
271
272
273
    /**
274
     * Query whether or not the message contained a signature at the root level when the object was constructed.
275
     *
276
     * @return bool
277
     */
278
    public function isMessageConstructedWithSignature(): bool
279
    {
280
        return $this->messageContainedSignatureUponConstruction;
281
    }
282
283
284
    /**
285
     * Retrieve the RelayState associated with this message.
286
     *
287
     * @return string|null The RelayState, or NULL if no RelayState is given
288
     */
289
    public function getRelayState(): ?string
290
    {
291
        return $this->relayState;
292
    }
293
294
295
    /**
296
     * Set the RelayState associated with this message.
297
     *
298
     * @param string|null $relayState The new RelayState
299
     */
300
    public function setRelayState(string $relayState = null): void
301
    {
302
        Assert::nullOrNotWhitespaceOnly($relayState);
303
304
        $this->relayState = $relayState;
305
    }
306
307
308
    /**
309
     * Get the XML element.
310
     *
311
     * @return \DOMElement
312
     */
313
    public function getXML(): DOMElement
314
    {
315
        return $this->xml;
316
    }
317
318
319
    /**
320
     * Set the XML element.
321
     *
322
     * @param \DOMElement $xml
323
     */
324
    protected function setXML(DOMElement $xml): void
325
    {
326
        $this->xml = $xml;
0 ignored issues
show
Bug Best Practice introduced by
The property xml does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
327
    }
328
329
330
    /**
331
     * @return \DOMElement
332
     */
333
    protected function getOriginalXML(): DOMElement
334
    {
335
        return $this->xml ?? $this->toUnsignedXML();
336
    }
337
338
339
    /**
340
     * @return array|null
341
     */
342
    public function getBlacklistedAlgorithms(): ?array
343
    {
344
        $container = ContainerSingleton::getInstance();
0 ignored issues
show
Bug introduced by
The type SimpleSAML\SAML2\XML\samlp\ContainerSingleton was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
345
        return $container->getBlacklistedEncryptionAlgorithms();
346
    }
347
348
349
    /**
350
     * Convert this message to an unsigned XML document.
351
     * This method does not sign the resulting XML document.
352
     *
353
     * @return \DOMElement The root element of the DOM tree
354
     */
355
    protected function toUnsignedXML(?DOMElement $parent = null): DOMElement
356
    {
357
        $root = $this->instantiateParentElement($parent);
358
359
        /* Ugly hack to add another namespace declaration to the root element. */
360
        $root->setAttributeNS(C::NS_SAML, 'saml:tmp', 'tmp');
361
        $root->removeAttributeNS(C::NS_SAML, 'tmp');
362
363
        $root->setAttribute('Version', $this->getVersion());
364
        $root->setAttribute('ID', $this->getId());
365
        $root->setAttribute('IssueInstant', gmdate('Y-m-d\TH:i:s\Z', $this->getIssueInstant()));
366
367
        if ($this->getDestination() !== null) {
368
            $root->setAttribute('Destination', $this->getDestination());
369
        }
370
371
        if ($this->getConsent() !== null && $this->getConsent() !== C::CONSENT_UNSPECIFIED) {
372
            $root->setAttribute('Consent', $this->getConsent());
373
        }
374
375
        $this->getIssuer()?->toXML($root);
376
377
        if ($this->getExtensions() !== null && !$this->getExtensions()->isEmptyElement()) {
378
            $this->getExtensions()->toXML($root);
379
        }
380
381
        return $root;
382
    }
383
384
385
    /**
386
     * Create XML from this class
387
     *
388
     * @param \DOMElement|null $parent
389
     * @return \DOMElement
390
     */
391
    public function toXML(?DOMElement $parent = null): DOMElement
392
    {
393
        if ($this->isSigned() === true && $this->signer === null) {
394
            // We already have a signed document and no signer was set to re-sign it
395
            if ($parent === null) {
396
                return $this->xml;
397
            }
398
399
            $node = $parent->ownerDocument?->importNode($this->xml, true);
400
            $parent->appendChild($node);
401
            return $parent;
402
        }
403
404
        $e = $this->toUnsignedXML($parent);
405
406
        if ($this->signer !== null) {
407
            $signedXML = $this->doSign($e);
408
409
            // Test for an Issuer
410
            $messageElements = XPath::xpQuery($signedXML, './saml_assertion:Issuer', XPath::getXPath($signedXML));
411
            $issuer = array_pop($messageElements);
412
413
            $signedXML->insertBefore($this->signature->toXML($signedXML), $issuer->nextSibling);
414
            return $signedXML;
415
        }
416
417
        return $e;
418
    }
419
}
420