Assertion::getConditions()   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\saml;
6
7
use DOMElement;
8
use SimpleSAML\SAML2\Assert\Assert;
9
use SimpleSAML\SAML2\Constants as C;
10
use SimpleSAML\SAML2\Exception\Protocol\RequestVersionTooHighException;
11
use SimpleSAML\SAML2\Exception\Protocol\RequestVersionTooLowException;
12
use SimpleSAML\SAML2\Type\SAMLDateTimeValue;
13
use SimpleSAML\SAML2\Type\SAMLStringValue;
14
use SimpleSAML\SAML2\Utils\XPath;
15
use SimpleSAML\SAML2\XML\EncryptableElementTrait;
16
use SimpleSAML\SAML2\XML\SignableElementTrait;
17
use SimpleSAML\SAML2\XML\SignedElementTrait;
18
use SimpleSAML\XML\SchemaValidatableElementInterface;
19
use SimpleSAML\XML\SchemaValidatableElementTrait;
20
use SimpleSAML\XMLSchema\Exception\InvalidDOMElementException;
21
use SimpleSAML\XMLSchema\Exception\MissingElementException;
22
use SimpleSAML\XMLSchema\Exception\TooManyElementsException;
23
use SimpleSAML\XMLSchema\Type\IDValue;
24
use SimpleSAML\XMLSecurity\Backend\EncryptionBackend;
25
use SimpleSAML\XMLSecurity\XML\ds\Signature;
26
use SimpleSAML\XMLSecurity\XML\EncryptableElementInterface;
27
use SimpleSAML\XMLSecurity\XML\SignableElementInterface;
28
use SimpleSAML\XMLSecurity\XML\SignedElementInterface;
29
30
use function array_filter;
31
use function array_merge;
32
use function array_pop;
33
use function array_values;
34
use function strval;
35
36
/**
37
 * Class representing a SAML 2 assertion.
38
 *
39
 * @package simplesamlphp/saml2
40
 */
41
final class Assertion extends AbstractSamlElement implements
42
    EncryptableElementInterface,
43
    SchemaValidatableElementInterface,
44
    SignableElementInterface,
45
    SignedElementInterface
46
{
47
    use EncryptableElementTrait {
0 ignored issues
show
Bug introduced by
The trait SimpleSAML\SAML2\XML\EncryptableElementTrait requires the property $ownerDocument which is not provided by SimpleSAML\SAML2\XML\saml\Assertion.
Loading history...
48
        EncryptableElementTrait::getBlacklistedAlgorithms insteadof SignedElementTrait;
49
        EncryptableElementTrait::getBlacklistedAlgorithms insteadof SignableElementTrait;
50
    }
51
52
53
    use SchemaValidatableElementTrait;
54
    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\saml\Assertion: $ownerDocument, $documentElement
Loading history...
55
    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\saml\Assertion: $ownerDocument, $documentElement
Loading history...
56
57
58
    /**
59
     * @var bool
60
     */
61
    protected bool $wasSignedAtConstruction = false;
62
63
    /**
64
     * The original signed XML
65
     *
66
     * @var \DOMElement
67
     */
68
    protected DOMElement $xml;
69
70
71
    /**
72
     * Assertion constructor.
73
     *
74
     * @param \SimpleSAML\SAML2\XML\saml\Issuer $issuer
75
     * @param \SimpleSAML\XMLSchema\Type\IDValue $id
76
     * @param \SimpleSAML\SAML2\Type\SAMLDateTimeValue $issueInstant
77
     * @param \SimpleSAML\SAML2\XML\saml\Subject|null $subject
78
     * @param \SimpleSAML\SAML2\XML\saml\Conditions|null $conditions
79
     * @param \SimpleSAML\SAML2\XML\saml\AbstractStatementType[] $statements
80
     */
81
    public function __construct(
82
        protected Issuer $issuer,
83
        protected SAMLDateTimeValue $issueInstant,
84
        protected IDValue $id,
85
        protected ?Subject $subject = null,
86
        protected ?Conditions $conditions = null,
87
        protected array $statements = [],
88
    ) {
89
        Assert::true(
90
            $subject || !empty($statements),
91
            "Either a <saml:Subject> or some statement must be present in a <saml:Assertion>",
92
        );
93
        Assert::maxCount($statements, C::UNBOUNDED_LIMIT);
94
        Assert::allIsInstanceOf($statements, AbstractStatementType::class);
95
    }
96
97
98
    /**
99
     * Collect the value of the subject
100
     *
101
     * @return \SimpleSAML\SAML2\XML\saml\Subject|null
102
     */
103
    public function getSubject(): ?Subject
104
    {
105
        return $this->subject;
106
    }
107
108
109
    /**
110
     * Collect the value of the conditions-property
111
     *
112
     * @return \SimpleSAML\SAML2\XML\saml\Conditions|null
113
     */
114
    public function getConditions(): ?Conditions
115
    {
116
        return $this->conditions;
117
    }
118
119
120
    /**
121
     * @return \SimpleSAML\SAML2\XML\saml\AttributeStatement[]
122
     */
123
    public function getAttributeStatements(): array
124
    {
125
        return array_values(array_filter($this->statements, function ($statement) {
126
            return $statement instanceof AttributeStatement;
127
        }));
128
    }
129
130
131
    /**
132
     * @return \SimpleSAML\SAML2\XML\saml\AuthnStatement[]
133
     */
134
    public function getAuthnStatements(): array
135
    {
136
        return array_values(array_filter($this->statements, function ($statement) {
137
            return $statement instanceof AuthnStatement;
138
        }));
139
    }
140
141
142
    /**
143
     * @return \SimpleSAML\SAML2\XML\saml\AbstractStatement[]
144
     */
145
    public function getStatements(): array
146
    {
147
        return array_values(array_filter($this->statements, function ($statement) {
148
            return $statement instanceof AbstractStatement;
149
        }));
150
    }
151
152
153
    /**
154
     * Retrieve the identifier of this assertion.
155
     *
156
     * @return \SimpleSAML\XMLSchema\Type\IDValue The identifier of this assertion.
157
     */
158
    public function getId(): IDValue
159
    {
160
        return $this->id;
161
    }
162
163
164
    /**
165
     * Retrieve the issue timestamp of this assertion.
166
     *
167
     * @return \SimpleSAML\SAML2\Type\SAMLDateTimeValue The issue timestamp of this assertion, as an UNIX timestamp.
168
     */
169
    public function getIssueInstant(): SAMLDateTimeValue
170
    {
171
        return $this->issueInstant;
172
    }
173
174
175
    /**
176
     * Retrieve the issuer if this assertion.
177
     *
178
     * @return \SimpleSAML\SAML2\XML\saml\Issuer The issuer of this assertion.
179
     */
180
    public function getIssuer(): Issuer
181
    {
182
        return $this->issuer;
183
    }
184
185
186
    /**
187
     * @return bool
188
     */
189
    public function wasSignedAtConstruction(): bool
190
    {
191
        return $this->wasSignedAtConstruction;
192
    }
193
194
195
    /**
196
     * Get the XML element.
197
     *
198
     * @return \DOMElement
199
     */
200
    public function getXML(): DOMElement
201
    {
202
        return $this->xml;
203
    }
204
205
206
    /**
207
     * Set the XML element.
208
     *
209
     * @param \DOMElement $xml
210
     */
211
    private function setXML(DOMElement $xml): void
212
    {
213
        $this->xml = $xml;
214
    }
215
216
217
    /**
218
     * @return \DOMElement
219
     */
220
    protected function getOriginalXML(): DOMElement
221
    {
222
        return $this->xml ?? $this->toUnsignedXML();
223
    }
224
225
226
    public function getEncryptionBackend(): ?EncryptionBackend
227
    {
228
        // return the encryption backend you want to use,
229
        // or null if you are fine with the default
230
        return null;
231
    }
232
233
234
    /**
235
     * Convert XML into an Assertion
236
     *
237
     * @param \DOMElement $xml The XML element we should load
238
     * @return static
239
     *
240
     * @throws \SimpleSAML\Assert\AssertionFailedException if assertions are false
241
     * @throws \SimpleSAML\XMLSchema\Exception\InvalidDOMElementException
242
     *   if the qualified name of the supplied element is wrong
243
     * @throws \SimpleSAML\XMLSchema\Exception\MissingAttributeException
244
     *   if the supplied element is missing one of the mandatory attributes
245
     * @throws \SimpleSAML\XMLSchema\Exception\MissingElementException
246
     *   if one of the mandatory child-elements is missing
247
     * @throws \SimpleSAML\XMLSchema\Exception\TooManyElementsException
248
     *   if too many child-elements of a type are specified
249
     * @throws \Exception
250
     */
251
    public static function fromXML(DOMElement $xml): static
252
    {
253
        Assert::same($xml->localName, 'Assertion', InvalidDOMElementException::class);
254
        Assert::same($xml->namespaceURI, Assertion::NS, InvalidDOMElementException::class);
255
256
        $version = self::getAttribute($xml, 'Version', SAMLStringValue::class);
257
        Assert::true(version_compare('2.0', strval($version), '<='), RequestVersionTooLowException::class);
258
        Assert::true(version_compare('2.0', strval($version), '>='), RequestVersionTooHighException::class);
259
260
        $issuer = Issuer::getChildrenOfClass($xml);
261
        Assert::minCount($issuer, 1, 'Missing <saml:Issuer> in assertion.', MissingElementException::class);
262
        Assert::maxCount($issuer, 1, 'More than one <saml:Issuer> in assertion.', TooManyElementsException::class);
263
264
        $subject = Subject::getChildrenOfClass($xml);
265
        Assert::maxCount(
266
            $subject,
267
            1,
268
            'More than one <saml:Subject> in <saml:Assertion>',
269
            TooManyElementsException::class,
270
        );
271
272
        $conditions = Conditions::getChildrenOfClass($xml);
273
        Assert::maxCount(
274
            $conditions,
275
            1,
276
            'More than one <saml:Conditions> in <saml:Assertion>.',
277
            TooManyElementsException::class,
278
        );
279
280
        $signature = Signature::getChildrenOfClass($xml);
281
        Assert::maxCount($signature, 1, 'Only one <ds:Signature> element is allowed.', TooManyElementsException::class);
282
283
        $authnStatement = AuthnStatement::getChildrenOfClass($xml);
284
        $attrStatement = AttributeStatement::getChildrenOfClass($xml);
285
        $statements = AbstractStatement::getChildrenOfClass($xml);
286
287
        $assertion = new static(
288
            array_pop($issuer),
289
            self::getAttribute($xml, 'IssueInstant', SAMLDateTimeValue::class),
290
            self::getAttribute($xml, 'ID', IDValue::class),
291
            array_pop($subject),
292
            array_pop($conditions),
293
            array_merge($authnStatement, $attrStatement, $statements),
294
        );
295
296
        if (!empty($signature)) {
297
            $assertion->setSignature($signature[0]);
298
            $assertion->wasSignedAtConstruction = true;
299
            $assertion->setXML($xml);
300
        }
301
302
        return $assertion;
303
    }
304
305
306
    /**
307
     * Convert this assertion to an unsigned XML document.
308
     * This method does not sign the resulting XML document.
309
     *
310
     * @return \DOMElement The root element of the DOM tree
311
     */
312
    protected function toUnsignedXML(?DOMElement $parent = null): DOMElement
313
    {
314
        $e = $this->instantiateParentElement($parent);
315
316
        $e->setAttribute('Version', '2.0');
317
        $e->setAttribute('ID', strval($this->getId()));
318
        $e->setAttribute('IssueInstant', strval($this->getIssueInstant()));
319
320
        $this->getIssuer()->toXML($e);
321
        $this->getSubject()?->toXML($e);
322
        $this->getConditions()?->toXML($e);
323
324
        foreach ($this->statements as $statement) {
325
            $statement->toXML($e);
326
        }
327
328
        return $e;
329
    }
330
331
332
    /**
333
     * Convert this assertion to a signed XML element, if a signer was set.
334
     *
335
     * @param \DOMElement|null $parent The DOM node the assertion should be created in.
336
     *
337
     * @return \DOMElement This assertion.
338
     * @throws \Exception
339
     */
340
    public function toXML(?DOMElement $parent = null): DOMElement
341
    {
342
        if ($this->isSigned() === true && $this->signer === null) {
343
            // We already have a signed document and no signer was set to re-sign it
344
            if ($parent === null) {
345
                return $this->getXML();
346
            }
347
348
            $node = $parent->ownerDocument?->importNode($this->getXML(), true);
349
            $parent->appendChild($node);
350
            return $parent;
351
        }
352
353
        $e = $this->toUnsignedXML($parent);
354
355
        if ($this->signer !== null) {
356
            $signedXML = $this->doSign($e);
357
358
            // Test for an Issuer
359
            $messageElements = XPath::xpQuery($signedXML, './saml_assertion:Issuer', XPath::getXPath($signedXML));
360
            $issuer = array_pop($messageElements);
361
362
            $signedXML->insertBefore($this->signature?->toXML($signedXML), $issuer->nextSibling);
363
            return $signedXML;
364
        }
365
366
        return $e;
367
    }
368
}
369