Passed
Push — master ( b3f66f...fee7a6 )
by Tim
14:24
created

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