Passed
Pull Request — master (#280)
by Tim
02:43 queued 22s
created

AbstractMessage::setXML()   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 1
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 DOMDocument;
8
use DOMElement;
9
use Exception;
10
use SimpleSAML\Assert\Assert;
11
use SimpleSAML\SAML2\Constants;
12
use SimpleSAML\SAML2\Utilities\Temporal;
13
use SimpleSAML\SAML2\Utils;
14
use SimpleSAML\SAML2\XML\ExtendableElementTrait;
15
use SimpleSAML\SAML2\XML\saml\Issuer;
16
use SimpleSAML\XMLSecurity\XML\SignableElementInterface;
17
use SimpleSAML\XMLSecurity\XML\SignableElementTrait;
18
use SimpleSAML\XMLSecurity\XML\SignedElementInterface;
19
use SimpleSAML\XMLSecurity\XML\SignedElementTrait;
20
use SimpleSAML\XMLSecurity\XMLSecurityKey;
21
22
use function array_pop;
23
use function call_user_func;
24
use function count;
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;
1 ignored issue
show
introduced by
The trait SimpleSAML\XMLSecurity\XML\SignableElementTrait requires some properties which are not provided by SimpleSAML\SAML2\XML\samlp\AbstractMessage: $ownerDocument, $documentElement
Loading history...
39
    use SignedElementTrait;
1 ignored issue
show
introduced by
The trait SimpleSAML\XMLSecurity\XML\SignedElementTrait requires some properties which are not provided by SimpleSAML\SAML2\XML\samlp\AbstractMessage: $ownerDocument, $documentElement
Loading history...
40
41
    /**
42
     * The identifier of this message.
43
     *
44
     * @var string
45
     */
46
    protected string $id;
47
48
    /** @var \DOMElement $xml */
49
    protected DOMElement $xml;
50
51
    /**
52
     * The version of this message.
53
     *
54
     * @var string
55
     */
56
    protected string $version = '2.0';
57
58
    /**
59
     * The issue timestamp of this message, as an UNIX timestamp.
60
     *
61
     * @var int
62
     */
63
    protected int $issueInstant;
64
65
    /**
66
     * The destination URL of this message if it is known.
67
     *
68
     * @var string|null
69
     */
70
    protected ?string $destination = null;
71
72
    /**
73
     * The destination URL of this message if it is known.
74
     *
75
     * @var string|null
76
     */
77
    protected ?string $consent;
78
79
    /**
80
     * The entity id of the issuer of this message, or null if unknown.
81
     *
82
     * @var \SimpleSAML\SAML2\XML\saml\Issuer|null
83
     */
84
    protected ?Issuer $issuer = null;
85
86
    /**
87
     * The RelayState associated with this message.
88
     *
89
     * @var string|null
90
     */
91
    protected ?string $relayState = null;
92
93
    /**
94
     * The \DOMDocument we are currently building.
95
     *
96
     * This variable is used while generating XML from this message. It holds the
97
     * \DOMDocument of the XML we are generating.
98
     *
99
     * @var \DOMDocument|null
100
     */
101
    protected ?DOMDocument $document = null;
102
103
    /** @var bool */
104
    protected bool $messageContainedSignatureUponConstruction = false;
105
106
    /**
107
     * Available methods for validating this message.
108
     *
109
     * @var array
110
     */
111
    private array $validators = [];
112
113
114
    /**
115
     * Initialize a message.
116
     *
117
     * @param \SimpleSAML\SAML2\XML\saml\Issuer|null $issuer
118
     * @param string|null $id
119
     * @param int|null $issueInstant
120
     * @param string|null $destination
121
     * @param string|null $consent
122
     * @param \SimpleSAML\SAML2\XML\samlp\Extensions $extensions
123
     * @param string|null $relayState
124
     *
125
     * @throws \Exception
126
     */
127
    protected function __construct(
128
        ?Issuer $issuer = null,
129
        ?string $id = null,
130
        ?int $issueInstant = null,
131
        ?string $destination = null,
132
        ?string $consent = null,
133
        ?Extensions $extensions = null,
134
        ?string $relayState = null
135
    ) {
136
        $this->setIssuer($issuer);
137
        $this->setId($id);
138
        $this->setIssueInstant($issueInstant);
139
        $this->setDestination($destination);
140
        $this->setConsent($consent);
141
        $this->setExtensions($extensions);
142
        $this->setRelayState($relayState);
143
        $this->addValidator([$this, 'xmlSignatureValidatorWrapper'], []);
144
    }
145
146
147
    /**
148
     * Add a method for validating this message.
149
     *
150
     * This function is used by the HTTP-Redirect binding, to make it possible to
151
     * check the signature against the one included in the query string.
152
     *
153
     * @param callable $function The function which should be called
154
     * @param mixed $data The data that should be included as the first parameter to the function
155
     */
156
    public function addValidator(callable $function, $data): void
157
    {
158
        $this->validators[] = [
159
            'Function' => $function,
160
            'Data' => $data,
161
        ];
162
    }
163
164
165
    /**
166
     * Validate this message against a public key.
167
     *
168
     * true is returned on success, false is returned if we don't have any
169
     * signature we can validate. An exception is thrown if the signature
170
     * validation fails.
171
     *
172
     * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key The key we should check against
173
     * @throws \Exception
174
     * @return bool true on success, false when we don't have a signature
175
     */
176
    public function validate(XMLSecurityKey $key): bool
177
    {
178
        if (count($this->validators) === 0) {
179
            return false;
180
        }
181
182
        $exceptions = [];
183
184
        foreach ($this->validators as $validator) {
185
            $function = $validator['Function'];
186
            $data = $validator['Data'];
187
188
            try {
189
                call_user_func($function, $data, $key);
190
                /* We were able to validate the message with this validator. */
191
192
                return true;
193
            } catch (Exception $e) {
194
                $exceptions[] = $e;
195
            }
196
        }
197
198
        Assert::notEmpty($exceptions);
199
200
        /**
201
         * No validators were able to validate the message.
202
         * @psalm-suppress InvalidThrow
203
         */
204
        throw array_pop($exceptions);
205
    }
206
207
208
    /**
209
     * Retrieve the identifier of this message.
210
     *
211
     * @return string The identifier of this message
212
     */
213
    public function getId(): string
214
    {
215
        return $this->id;
216
    }
217
218
219
    /**
220
     * Set the identifier of this message.
221
     *
222
     * @param string|null $id The new identifier of this message
223
     */
224
    private function setId(?string $id): void
225
    {
226
        Assert::nullOrNotWhitespaceOnly($id);
227
228
        if ($id === null) {
229
            $id = Utils::getContainer()->generateId();
230
        }
231
232
        $this->id = $id;
233
    }
234
235
236
    /**
237
     * Retrieve the version of this message.
238
     *
239
     * @return string The version of this message
240
     */
241
    public function getVersion(): string
242
    {
243
        return $this->version;
244
    }
245
246
247
    /**
248
     * Retrieve the issue timestamp of this message.
249
     *
250
     * @return int The issue timestamp of this message, as an UNIX timestamp
251
     */
252
    public function getIssueInstant(): int
253
    {
254
        return $this->issueInstant;
255
    }
256
257
258
    /**
259
     * Set the issue timestamp of this message.
260
     *
261
     * @param int|null $issueInstant The new issue timestamp of this message, as an UNIX timestamp
262
     */
263
    private function setIssueInstant(?int $issueInstant): void
264
    {
265
        if ($issueInstant === null) {
266
            $issueInstant = Temporal::getTime();
267
        }
268
269
        $this->issueInstant = $issueInstant;
270
    }
271
272
273
    /**
274
     * Retrieve the destination of this message.
275
     *
276
     * @return string|null The destination of this message, or NULL if no destination is given
277
     */
278
    public function getDestination(): ?string
279
    {
280
        return $this->destination;
281
    }
282
283
284
    /**
285
     * Set the destination of this message.
286
     *
287
     * @param string|null $destination The new destination of this message
288
     */
289
    private function setDestination(string $destination = null): void
290
    {
291
        Assert::nullOrNotWhitespaceOnly($destination);
292
293
        $this->destination = $destination;
294
    }
295
296
297
    /**
298
     * Get the given consent for this message.
299
     * Most likely (though not required) a value of urn:oasis:names:tc:SAML:2.0:consent.
300
     *
301
     * @see \SimpleSAML\SAML2\Constants
302
     * @return string|null Consent
303
     */
304
    public function getConsent(): ?string
305
    {
306
        return $this->consent;
307
    }
308
309
310
    /**
311
     * Set the given consent for this message.
312
     * Most likely (though not required) a value of urn:oasis:names:tc:SAML:2.0:consent.
313
     *
314
     * @see \SimpleSAML\SAML2\Constants
315
     * @param string|null $consent
316
     */
317
    private function setConsent(?string $consent): void
318
    {
319
        Assert::nullOrNotWhitespaceOnly($consent);
320
321
        $this->consent = $consent;
322
    }
323
324
325
    /**
326
     * Retrieve the issuer if this message.
327
     *
328
     * @return \SimpleSAML\SAML2\XML\saml\Issuer|null The issuer of this message, or NULL if no issuer is given
329
     */
330
    public function getIssuer(): ?Issuer
331
    {
332
        return $this->issuer;
333
    }
334
335
336
    /**
337
     * Set the issuer of this message.
338
     *
339
     * @param \SimpleSAML\SAML2\XML\saml\Issuer|null $issuer The new issuer of this message
340
     */
341
    private function setIssuer(Issuer $issuer = null): void
342
    {
343
        $this->issuer = $issuer;
344
    }
345
346
347
    /**
348
     * Query whether or not the message contained a signature at the root level when the object was constructed.
349
     *
350
     * @return bool
351
     */
352
    public function isMessageConstructedWithSignature(): bool
353
    {
354
        return $this->messageContainedSignatureUponConstruction;
355
    }
356
357
358
    /**
359
     * Retrieve the RelayState associated with this message.
360
     *
361
     * @return string|null The RelayState, or NULL if no RelayState is given
362
     */
363
    public function getRelayState(): ?string
364
    {
365
        return $this->relayState;
366
    }
367
368
369
    /**
370
     * Set the RelayState associated with this message.
371
     *
372
     * @param string|null $relayState The new RelayState
373
     */
374
    public function setRelayState(string $relayState = null): void
375
    {
376
        Assert::nullOrNotWhitespaceOnly($relayState);
377
378
        $this->relayState = $relayState;
379
    }
380
381
382
    /**
383
     * Wrapper method over SignedElementTrait to use as a validator for enveloped XML signatures.
384
     *
385
     * @param array $_
386
     * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key The key to use to verify the enveloped signature.
387
     *
388
     * @throws \Exception If there's no enveloped signature, or it fails to validate.
389
     */
390
    protected function xmlSignatureValidatorWrapper(array $_, XMLSecurityKey $key): void
391
    {
392
        if ($this->validateEnvelopedXmlSignature($key) === false) {
0 ignored issues
show
Bug introduced by
The method validateEnvelopedXmlSignature() does not exist on SimpleSAML\SAML2\XML\samlp\AbstractMessage. Did you maybe mean validate()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

392
        if ($this->/** @scrutinizer ignore-call */ validateEnvelopedXmlSignature($key) === false) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
393
            throw new Exception('No enveloped signature found');
394
        }
395
    }
396
397
398
    /**
399
     * Get the XML element.
400
     *
401
     * @return \DOMElement
402
     */
403
    public function getXML(): DOMElement
404
    {
405
        return $this->xml;
406
    }
407
408
409
    /**
410
     * Set the XML element.
411
     *
412
     * @param \DOMElement $xml
413
     */
414
    protected function setXML(DOMElement $xml): void
415
    {
416
        $this->xml = $xml;
417
    }
418
419
420
    /**
421
     * @inheritDoc
422
     */
423
    protected function getOriginalXML(): DOMElement
424
    {
425
        return $this->xml;
426
    }
427
428
429
    /**
430
     * Convert this message to an unsigned XML document.
431
     * This method does not sign the resulting XML document.
432
     *
433
     * @return \DOMElement The root element of the DOM tree
434
     */
435
    public function toXML(?DOMElement $parent = null): DOMElement
436
    {
437
        $root = $this->instantiateParentElement($parent);
438
439
        /* Ugly hack to add another namespace declaration to the root element. */
440
        $root->setAttributeNS(Constants::NS_SAML, 'saml:tmp', 'tmp');
441
        $root->removeAttributeNS(Constants::NS_SAML, 'tmp');
442
443
        $root->setAttribute('Version', $this->version);
444
        $root->setAttribute('ID', $this->id);
445
        $root->setAttribute('IssueInstant', gmdate('Y-m-d\TH:i:s\Z', $this->issueInstant));
446
447
        if ($this->destination !== null) {
448
            $root->setAttribute('Destination', $this->destination);
449
        }
450
        if ($this->consent !== null && $this->consent !== Constants::CONSENT_UNSPECIFIED) {
451
            $root->setAttribute('Consent', $this->consent);
452
        }
453
454
        if ($this->issuer !== null) {
455
            $this->issuer->toXML($root);
456
        }
457
458
        if ($this->Extensions !== null && !$this->Extensions->isEmptyElement()) {
459
            $this->Extensions->toXML($root);
460
        }
461
462
        return $root;
463
    }
464
}
465