AbstractAssertionType::toXML()   B
last analyzed

Complexity

Conditions 6
Paths 5

Size

Total Lines 36
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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