AbstractAssertionType::getAllStatements()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 1
c 1
b 0
f 1
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\SAML11\XML\saml;
6
7
use DOMElement;
8
use SimpleSAML\SAML11\Assert\Assert;
9
use SimpleSAML\SAML11\Compat\ContainerSingleton;
10
use SimpleSAML\SAML11\Constants as C;
11
use SimpleSAML\SAML11\Exception\VersionMismatchException;
12
use SimpleSAML\SAML11\Type\SAMLDateTimeValue;
13
use SimpleSAML\SAML11\Type\SAMLStringValue;
14
use SimpleSAML\SAML11\Utils\XPath;
15
use SimpleSAML\XMLSchema\Exception\InvalidDOMElementException;
16
use SimpleSAML\XMLSchema\Exception\MissingElementException;
17
use SimpleSAML\XMLSchema\Exception\SchemaViolationException;
18
use SimpleSAML\XMLSchema\Exception\TooManyElementsException;
19
use SimpleSAML\XMLSchema\Type\IDValue;
20
use SimpleSAML\XMLSchema\Type\NonNegativeIntegerValue;
21
use SimpleSAML\XMLSecurity\XML\ds\Signature;
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_filter;
28
use function array_merge;
29
use function array_pop;
30
use function array_values;
31
use function strval;
32
33
/**
34
 * SAML Assertion Type abstract data type.
35
 *
36
 * @package simplesamlphp/saml11
37
 */
38
abstract class AbstractAssertionType extends AbstractSamlElement implements
39
    SignableElementInterface,
40
    SignedElementInterface
41
{
42
    use SignableElementTrait;
43
    use SignedElementTrait;
44
45
46
    /**
47
     * The original signed XML
48
     *
49
     * @var \DOMElement
50
     */
51
    protected DOMElement $xml;
52
53
54
    /**
55
     * Initialize a saml:AssertionType from scratch
56
     *
57
     * @param \SimpleSAML\XMLSchema\Type\NonNegativeIntegerValue $majorVersion
58
     * @param \SimpleSAML\XMLSchema\Type\NonNegativeIntegerValue $minorVersion
59
     * @param \SimpleSAML\XMLSchema\Type\IDValue $assertionID
60
     * @param \SimpleSAML\SAML11\Type\SAMLStringValue $issuer
61
     * @param \SimpleSAML\SAML11\Type\SAMLDateTimeValue $issueInstant
62
     * @param \SimpleSAML\SAML11\XML\saml\Conditions|null $conditions
63
     * @param \SimpleSAML\SAML11\XML\saml\Advice|null $advice
64
     * @param array<\SimpleSAML\SAML11\XML\saml\AbstractStatementType> $statements
65
     */
66
    final public function __construct(
67
        protected NonNegativeIntegerValue $majorVersion,
68
        protected NonNegativeIntegerValue $minorVersion,
69
        protected IDValue $assertionID,
70
        protected SAMLStringValue $issuer,
71
        protected SAMLDateTimeValue $issueInstant,
72
        protected ?Conditions $conditions = null,
73
        protected ?Advice $advice = null,
74
        protected array $statements = [],
75
    ) {
76
        Assert::same($majorVersion->getValue(), '1', VersionMismatchException::class);
77
        Assert::same($minorVersion->getValue(), '1', VersionMismatchException::class);
78
        Assert::minCount($statements, 1, MissingElementException::class);
79
        Assert::maxCount($statements, C::UNBOUNDED_LIMIT);
80
        Assert::allIsInstanceOf($statements, AbstractStatementType::class, SchemaViolationException::class);
81
    }
82
83
84
    /**
85
     * Collect the value of the majorVersion-property
86
     *
87
     * @return \SimpleSAML\XMLSchema\Type\NonNegativeIntegerValue
88
     */
89
    public function getMajorVersion(): NonNegativeIntegerValue
90
    {
91
        return $this->majorVersion;
92
    }
93
94
95
    /**
96
     * Collect the value of the minorVersion-property
97
     *
98
     * @return \SimpleSAML\XMLSchema\Type\NonNegativeIntegerValue
99
     */
100
    public function getMinorVersion(): NonNegativeIntegerValue
101
    {
102
        return $this->minorVersion;
103
    }
104
105
106
    /**
107
     * Collect the value of the assertionID-property
108
     *
109
     * Note: the name of this method is not consistent, but it has to be named getId for xml-security to work.
110
     *
111
     * @return \SimpleSAML\XMLSchema\Type\IDValue
112
     */
113
    public function getId(): IDValue
114
    {
115
        return $this->assertionID;
116
    }
117
118
119
    /**
120
     * Collect the value of the issuer-property
121
     *
122
     * @return \SimpleSAML\SAML11\Type\SAMLStringValue
123
     */
124
    public function getIssuer(): SAMLStringValue
125
    {
126
        return $this->issuer;
127
    }
128
129
130
    /**
131
     * Collect the value of the issueInstant-property
132
     *
133
     * @return \SimpleSAML\SAML11\Type\SAMLDateTimeValue
134
     */
135
    public function getIssueInstant(): SAMLDateTimeValue
136
    {
137
        return $this->issueInstant;
138
    }
139
140
141
    /**
142
     * Collect the value of the conditions-property
143
     *
144
     * @return \SimpleSAML\SAML11\XML\saml\Conditions|null
145
     */
146
    public function getConditions(): ?Conditions
147
    {
148
        return $this->conditions;
149
    }
150
151
152
    /**
153
     * Collect the value of the advice-property
154
     *
155
     * @return \SimpleSAML\SAML11\XML\saml\Advice|null
156
     */
157
    public function getAdvice(): ?Advice
158
    {
159
        return $this->advice;
160
    }
161
162
163
    /**
164
     * Collect the value of the statements-property
165
     *
166
     * @return array<\SimpleSAML\SAML11\XML\saml\AbstractStatementType>
167
     */
168
    public function getAllStatements(): array
169
    {
170
        return $this->statements;
171
    }
172
173
174
    /**
175
     * @return \SimpleSAML\SAML11\XML\saml\AbstractStatement[]
176
     */
177
    public function getStatements(): array
178
    {
179
        return array_values(array_filter($this->statements, function ($statement) {
180
            return $statement instanceof AbstractStatement;
181
        }));
182
    }
183
184
185
    /**
186
     * @return \SimpleSAML\SAML11\XML\saml\AbstractSubjectStatement[]
187
     */
188
    public function getSubjectStatements(): array
189
    {
190
        return array_values(array_filter($this->statements, function ($statement) {
191
            return $statement instanceof AbstractSubjectStatement;
192
        }));
193
    }
194
195
196
    /**
197
     * @return \SimpleSAML\SAML11\XML\saml\AuthenticationStatement[]
198
     */
199
    public function getAuthenticationStatements(): array
200
    {
201
        return array_values(array_filter($this->statements, function ($statement) {
202
            return $statement instanceof AuthenticationStatement;
203
        }));
204
    }
205
206
207
    /**
208
     * @return \SimpleSAML\SAML11\XML\saml\AuthorizationDecisionStatement[]
209
     */
210
    public function getAuthorizationDecisionStatements(): array
211
    {
212
        return array_values(array_filter($this->statements, function ($statement) {
213
            return $statement instanceof AuthorizationDecisionStatement;
214
        }));
215
    }
216
217
218
    /**
219
     * @return \SimpleSAML\SAML11\XML\saml\AttributeStatement[]
220
     */
221
    public function getAttributeStatements(): array
222
    {
223
        return array_values(array_filter($this->statements, function ($statement) {
224
            return $statement instanceof AttributeStatement;
225
        }));
226
    }
227
228
229
    /**
230
     * Set the XML element.
231
     *
232
     * @param \DOMElement $xml
233
     */
234
    private function setOriginalXML(DOMElement $xml): void
235
    {
236
        $this->xml = $xml;
237
    }
238
239
240
    /**
241
     * @return \DOMElement
242
     */
243
    protected function getOriginalXML(): DOMElement
244
    {
245
        return $this->xml ?? $this->toUnsignedXML();
246
    }
247
248
249
    public function getBlacklistedAlgorithms(): ?array
250
    {
251
        $container = ContainerSingleton::getInstance();
252
        return $container->getBlacklistedEncryptionAlgorithms();
253
    }
254
255
256
    /**
257
     * Convert XML into an AssertionType
258
     *
259
     * @param \DOMElement $xml The XML element we should load
260
     * @return static
261
     *
262
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException
263
     *   if the qualified name of the supplied element is wrong
264
     */
265
    public static function fromXML(DOMElement $xml): static
266
    {
267
        Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class);
268
        Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class);
269
270
        $conditions = Conditions::getChildrenOfClass($xml);
271
        Assert::maxCount(
272
            $conditions,
273
            1,
274
            'More than one <saml:Conditions> in <saml:Assertion>.',
275
            TooManyElementsException::class,
276
        );
277
278
        $advice = Advice::getChildrenOfClass($xml);
279
        Assert::maxCount(
280
            $advice,
281
            1,
282
            'More than one <saml:Advice> in <saml:Assertion>.',
283
            TooManyElementsException::class,
284
        );
285
286
        $statements = AbstractStatement::getChildrenOfClass($xml);
287
        $subjectStatement = AbstractSubjectStatement::getChildrenOfClass($xml);
288
        $authnStatement = AuthenticationStatement::getChildrenOfClass($xml);
289
        $authzDecisionStatement = AuthorizationDecisionStatement::getChildrenOfClass($xml);
290
        $attrStatement = AttributeStatement::getChildrenOfClass($xml);
291
292
        $signature = Signature::getChildrenOfClass($xml);
293
        Assert::maxCount($signature, 1, 'Only one <ds:Signature> element is allowed.', TooManyElementsException::class);
294
295
        $assertion = new static(
296
            self::getAttribute($xml, 'MajorVersion', NonNegativeIntegerValue::class),
297
            self::getAttribute($xml, 'MinorVersion', NonNegativeIntegerValue::class),
298
            self::getAttribute($xml, 'AssertionID', IDValue::class),
299
            self::getAttribute($xml, 'Issuer', SAMLStringValue::class),
300
            self::getAttribute($xml, 'IssueInstant', SAMLDateTimeValue::class),
301
            array_pop($conditions),
302
            array_pop($advice),
303
            array_merge($statements, $subjectStatement, $authnStatement, $authzDecisionStatement, $attrStatement),
304
        );
305
306
        if (!empty($signature)) {
307
            $assertion->setSignature($signature[0]);
308
            $assertion->setOriginalXML($xml);
309
        }
310
311
        return $assertion;
312
    }
313
314
315
    /**
316
     * Convert this assertion to an unsigned XML document.
317
     * This method does not sign the resulting XML document.
318
     *
319
     * @return \DOMElement The root element of the DOM tree
320
     */
321
    protected function toUnsignedXML(?DOMElement $parent = null): DOMElement
322
    {
323
        $e = $this->instantiateParentElement($parent);
324
325
        $e->setAttribute('MajorVersion', strval($this->getMajorVersion()));
326
        $e->setAttribute('MinorVersion', strval($this->getMinorVersion()));
327
        $e->setAttribute('AssertionID', strval($this->getId()));
328
        $e->setAttribute('Issuer', strval($this->getIssuer()));
329
        $e->setAttribute('IssueInstant', strval($this->getIssueInstant()));
330
331
        $this->getConditions()?->toXML($e);
332
        $this->getAdvice()?->toXML($e);
333
334
        foreach ($this->getAllStatements() as $statement) {
335
            $statement->toXML($e);
336
        }
337
338
        return $e;
339
    }
340
341
342
    /**
343
     * Convert this assertion to a signed XML element, if a signer was set.
344
     *
345
     * @param \DOMElement|null $parent The DOM node the assertion should be created in.
346
     *
347
     * @return \DOMElement This assertion.
348
     * @throws \Exception
349
     */
350
    public function toXML(?DOMElement $parent = null): DOMElement
351
    {
352
        if ($this->isSigned() === true && $this->signer === null) {
353
            // We already have a signed document and no signer was set to re-sign it
354
            if ($parent === null) {
355
                return $this->getOriginalXML();
356
            }
357
358
            $node = $parent->ownerDocument?->importNode($this->getOriginalXML(), true);
359
            $parent->appendChild($node);
360
            return $parent;
361
        }
362
363
        $e = $this->toUnsignedXML($parent);
364
365
        if ($this->signer !== null) {
366
            $signedXML = $this->doSign($e);
367
368
            // Test for last element, if any
369
            $assertionElements = XPath::xpQuery(
370
                $signedXML,
371
                './saml_assertion/following-sibling::*[position() = last()]',
372
                XPath::getXPath($signedXML),
373
            );
374
            $last = array_pop($assertionElements);
375
376
            if ($last !== null) {
377
                $signedXML->insertBefore($this->signature?->toXML($signedXML), $last->nextSibling);
378
            } else {
379
                $signedXML->appendChild($this->signature?->toXML($signedXML));
380
            }
381
382
            return $signedXML;
383
        }
384
385
        return $e;
386
    }
387
}
388