Assertion::toUnsignedXML()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 10
nc 2
nop 1
dl 0
loc 17
rs 9.9332
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;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\SAML2\Constants was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use SimpleSAML\SAML2\Exception\Protocol\RequestVersionTooHighException;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\SAML2\Excepti...VersionTooHighException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
11
use SimpleSAML\SAML2\Exception\Protocol\RequestVersionTooLowException;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\SAML2\Excepti...tVersionTooLowException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
12
use SimpleSAML\SAML2\Exception\ProtocolViolationException;
13
use SimpleSAML\SAML2\Type\SAMLDateTimeValue;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\SAML2\Type\SAMLDateTimeValue was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
14
use SimpleSAML\SAML2\Type\SAMLStringValue;
15
use SimpleSAML\SAML2\Utils\XPath;
16
use SimpleSAML\SAML2\XML\EncryptableElementTrait;
17
use SimpleSAML\SAML2\XML\SignableElementTrait;
18
use SimpleSAML\SAML2\XML\SignedElementTrait;
19
use SimpleSAML\XML\SchemaValidatableElementInterface;
20
use SimpleSAML\XML\SchemaValidatableElementTrait;
21
use SimpleSAML\XMLSchema\Exception\InvalidDOMElementException;
22
use SimpleSAML\XMLSchema\Exception\MissingElementException;
23
use SimpleSAML\XMLSchema\Exception\TooManyElementsException;
24
use SimpleSAML\XMLSchema\Type\IDValue;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\XMLSchema\Type\IDValue was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
25
use SimpleSAML\XMLSecurity\Backend\EncryptionBackend;
26
use SimpleSAML\XMLSecurity\XML\ds\Signature;
27
use SimpleSAML\XMLSecurity\XML\EncryptableElementInterface;
28
use SimpleSAML\XMLSecurity\XML\SignableElementInterface;
29
use SimpleSAML\XMLSecurity\XML\SignedElementInterface;
30
31
use function array_filter;
32
use function array_merge;
33
use function array_pop;
34
use function array_values;
35
use function count;
36
use function strval;
37
38
/**
39
 * Class representing a SAML 2 assertion.
40
 *
41
 * @package simplesamlphp/saml2
42
 */
43
final class Assertion extends AbstractSamlElement implements
0 ignored issues
show
Bug introduced by
The type SimpleSAML\SAML2\XML\saml\AbstractSamlElement was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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