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