Passed
Pull Request — master (#226)
by Jaime Pérez
03:57 queued 01:05
created

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