AbstractMessage::getVersion()   A
last analyzed

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 DateTimeImmutable;
8
use DOMElement;
9
use SimpleSAML\SAML2\Assert\Assert;
10
use SimpleSAML\SAML2\Constants as C;
11
use SimpleSAML\SAML2\Exception\ProtocolViolationException;
12
use SimpleSAML\SAML2\Utils;
13
use SimpleSAML\SAML2\Utils\XPath;
14
use SimpleSAML\SAML2\XML\ExtendableElementTrait;
15
use SimpleSAML\SAML2\XML\saml\Issuer;
16
use SimpleSAML\SAML2\XML\SignableElementTrait;
17
use SimpleSAML\SAML2\XML\SignedElementTrait;
18
use SimpleSAML\XML\Utils\Random as RandomUtils;
19
use SimpleSAML\XMLSecurity\XML\SignableElementInterface;
20
use SimpleSAML\XMLSecurity\XML\SignedElementInterface;
21
22
use function array_pop;
23
24
/**
25
 * Base class for all SAML 2 messages.
26
 *
27
 * Implements what is common between the samlp:RequestAbstractType and
28
 * samlp:StatusResponseType element types.
29
 *
30
 * @package simplesamlphp/saml2
31
 */
32
abstract class AbstractMessage extends AbstractSamlpElement implements SignableElementInterface, SignedElementInterface
33
{
34
    use ExtendableElementTrait;
35
    use SignableElementTrait;
0 ignored issues
show
introduced by
The trait SimpleSAML\SAML2\XML\SignableElementTrait requires some properties which are not provided by SimpleSAML\SAML2\XML\samlp\AbstractMessage: $ownerDocument, $documentElement
Loading history...
36
    use SignedElementTrait {
0 ignored issues
show
introduced by
The trait SimpleSAML\SAML2\XML\SignedElementTrait requires some properties which are not provided by SimpleSAML\SAML2\XML\samlp\AbstractMessage: $ownerDocument, $documentElement
Loading history...
37
        SignedElementTrait::getBlacklistedAlgorithms insteadof SignableElementTrait;
38
    }
39
40
41
    /** @var bool */
42
    protected bool $messageContainedSignatureUponConstruction = false;
43
44
    /**
45
     * The original signed XML
46
     *
47
     * @var \DOMElement
48
     * @psalm-suppress PropertyNotSetInConstructor
49
     */
50
    protected DOMElement $xml;
51
52
53
    /**
54
     * Initialize a message.
55
     *
56
     * @param \SimpleSAML\SAML2\XML\saml\Issuer|null $issuer
57
     * @param string|null $id
58
     * @param string $version
59
     * @param \DateTimeImmutable|null $issueInstant
60
     * @param string|null $destination
61
     * @param string|null $consent
62
     * @param \SimpleSAML\SAML2\XML\samlp\Extensions $extensions
63
     *
64
     * @throws \Exception
65
     */
66
    protected function __construct(
67
        protected ?Issuer $issuer = null,
68
        protected ?string $id = null,
69
        protected string $version = '2.0',
70
        protected ?DateTimeImmutable $issueInstant = null,
71
        protected ?string $destination = null,
72
        protected ?string $consent = null,
73
        ?Extensions $extensions = null,
74
    ) {
75
        Assert::nullOrSame($issueInstant?->getTimeZone()->getName(), 'Z', ProtocolViolationException::class);
76
        Assert::nullOrValidNCName($id); // Covers the empty string
77
        Assert::nullOrValidURI($destination);
78
        Assert::nullOrValidURI($consent);
79
80
        $this->setExtensions($extensions);
81
    }
82
83
84
    /**
85
     * Retrieve the identifier of this message.
86
     *
87
     * @return string The identifier of this message
88
     */
89
    public function getId(): string
90
    {
91
        if ($this->id === null) {
92
            return (new RandomUtils())->generateId();
93
        }
94
95
        return $this->id;
96
    }
97
98
99
    /**
100
     * Retrieve the version of this message.
101
     *
102
     * @return string The version of this message
103
     */
104
    public function getVersion(): string
105
    {
106
        return $this->version;
107
    }
108
109
110
    /**
111
     * Retrieve the issue timestamp of this message.
112
     *
113
     * @return \DateTimeImmutable The issue timestamp of this message, as an UNIX timestamp
114
     */
115
    public function getIssueInstant(): DateTimeImmutable
116
    {
117
        if ($this->issueInstant === null) {
118
            return Utils::getContainer()->getClock()->now();
119
        }
120
121
        return $this->issueInstant;
122
    }
123
124
125
    /**
126
     * Retrieve the destination of this message.
127
     *
128
     * @return string|null The destination of this message, or NULL if no destination is given
129
     */
130
    public function getDestination(): ?string
131
    {
132
        return $this->destination;
133
    }
134
135
136
    /**
137
     * Get the given consent for this message.
138
     * Most likely (though not required) a value of urn:oasis:names:tc:SAML:2.0:consent.
139
     *
140
     * @see \SimpleSAML\SAML2\Constants
141
     * @return string|null Consent
142
     */
143
    public function getConsent(): ?string
144
    {
145
        return $this->consent;
146
    }
147
148
149
    /**
150
     * Retrieve the issuer if this message.
151
     *
152
     * @return \SimpleSAML\SAML2\XML\saml\Issuer|null The issuer of this message, or NULL if no issuer is given
153
     */
154
    public function getIssuer(): ?Issuer
155
    {
156
        return $this->issuer;
157
    }
158
159
160
    /**
161
     * Query whether or not the message contained a signature at the root level when the object was constructed.
162
     *
163
     * @return bool
164
     */
165
    public function isMessageConstructedWithSignature(): bool
166
    {
167
        return $this->messageContainedSignatureUponConstruction;
168
    }
169
170
171
    /**
172
     * Get the XML element.
173
     *
174
     * @return \DOMElement
175
     */
176
    public function getXML(): DOMElement
177
    {
178
        return $this->xml;
179
    }
180
181
182
    /**
183
     * Set the XML element.
184
     *
185
     * @param \DOMElement $xml
186
     */
187
    protected function setXML(DOMElement $xml): void
188
    {
189
        $this->xml = $xml;
190
    }
191
192
193
    /**
194
     * @return \DOMElement
195
     */
196
    protected function getOriginalXML(): DOMElement
197
    {
198
        return $this->xml ?? $this->toUnsignedXML();
199
    }
200
201
202
    /**
203
     * Convert this message to an unsigned XML document.
204
     * This method does not sign the resulting XML document.
205
     *
206
     * @return \DOMElement The root element of the DOM tree
207
     */
208
    protected function toUnsignedXML(?DOMElement $parent = null): DOMElement
209
    {
210
        $root = $this->instantiateParentElement($parent);
211
212
        /* Ugly hack to add another namespace declaration to the root element. */
213
        $root->setAttributeNS(C::NS_SAML, 'saml:tmp', 'tmp');
214
        $root->removeAttributeNS(C::NS_SAML, 'tmp');
215
216
        $root->setAttribute('Version', $this->getVersion());
217
        $root->setAttribute('ID', $this->getId());
218
        $root->setAttribute('IssueInstant', $this->getIssueInstant()->format(C::DATETIME_FORMAT));
219
220
        if ($this->getDestination() !== null) {
221
            $root->setAttribute('Destination', $this->getDestination());
222
        }
223
224
        if ($this->getConsent() !== null && $this->getConsent() !== C::CONSENT_UNSPECIFIED) {
225
            $root->setAttribute('Consent', $this->getConsent());
226
        }
227
228
        $this->getIssuer()?->toXML($root);
229
230
        $extensions = $this->getExtensions();
231
        if ($extensions !== null && !$extensions->isEmptyElement()) {
232
            $extensions->toXML($root);
233
        }
234
235
        return $root;
236
    }
237
238
239
    /**
240
     * Create XML from this class
241
     *
242
     * @param \DOMElement|null $parent
243
     * @return \DOMElement
244
     */
245
    public function toXML(?DOMElement $parent = null): DOMElement
246
    {
247
        if ($this->isSigned() === true && $this->signer === null) {
248
            // We already have a signed document and no signer was set to re-sign it
249
            if ($parent === null) {
250
                return $this->xml;
251
            }
252
253
            $node = $parent->ownerDocument?->importNode($this->getXML(), true);
254
            $parent->appendChild($node);
255
            return $parent;
256
        }
257
258
        $e = $this->toUnsignedXML($parent);
259
260
        if ($this->signer !== null) {
261
            $signedXML = $this->doSign($e);
262
263
            // Test for an Issuer
264
            $messageElements = XPath::xpQuery($signedXML, './saml_assertion:Issuer', XPath::getXPath($signedXML));
265
            $issuer = array_pop($messageElements);
266
267
            $signedXML->insertBefore($this->signature?->toXML($signedXML), $issuer->nextSibling);
268
            return $signedXML;
269
        }
270
271
        return $e;
272
    }
273
}
274