Assertion   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 310
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 88
dl 0
loc 310
rs 10
c 0
b 0
f 0
wmc 24

17 Methods

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