Passed
Push — master ( 2c8214...61ac29 )
by Tim
02:10
created

Assertion::setXML()   A

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 1
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 Exception;
9
use SimpleSAML\Assert\Assert;
10
use SimpleSAML\SAML2\Constants;
11
use SimpleSAML\SAML2\Exception\ProtocolViolationException;
12
use SimpleSAML\SAML2\Utilities\Temporal;
13
use SimpleSAML\SAML2\Utils;
14
use SimpleSAML\SAML2\Utils\XPath;
15
use SimpleSAML\XML\Chunk;
16
use SimpleSAML\XML\DOMDocumentFactory;
17
use SimpleSAML\XML\Exception\InvalidDOMElementException;
18
use SimpleSAML\XML\Exception\MissingElementException;
19
use SimpleSAML\XML\Exception\TooManyElementsException;
20
use SimpleSAML\XML\Utils as XMLUtils;
21
use SimpleSAML\XMLSecurity\Utils\Security as SecurityUtils;
22
use SimpleSAML\XMLSecurity\XML\ds\Signature;
23
use SimpleSAML\XMLSecurity\XML\SignableElementInterface;
24
use SimpleSAML\XMLSecurity\XML\SignableElementTrait;
25
use SimpleSAML\XMLSecurity\XML\SignedElementInterface;
26
use SimpleSAML\XMLSecurity\XML\SignedElementTrait;
27
use SimpleSAML\XMLSecurity\XMLSecEnc;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\XMLSecurity\XMLSecEnc 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...
28
use SimpleSAML\XMLSecurity\XMLSecurityKey;
29
30
use function array_filter;
31
use function array_merge;
32
use function array_pop;
33
use function array_values;
34
35
/**
36
 * Class representing a SAML 2 assertion.
37
 *
38
 * @package simplesamlphp/saml2
39
 */
40
class Assertion extends AbstractSamlElement implements SignableElementInterface, SignedElementInterface
41
{
42
    use SignableElementTrait;
43
    use SignedElementTrait;
1 ignored issue
show
introduced by
The trait SimpleSAML\XMLSecurity\XML\SignedElementTrait requires some properties which are not provided by SimpleSAML\SAML2\XML\saml\Assertion: $ownerDocument, $documentElement
Loading history...
44
45
    /**
46
     * The identifier of this assertion.
47
     *
48
     * @var string
49
     */
50
    protected string $id;
51
52
    /**
53
     * The issue timestamp of this assertion, as an UNIX timestamp.
54
     *
55
     * @var int
56
     */
57
    protected int $issueInstant;
58
59
    /**
60
     * The issuer of this assertion.
61
     *
62
     * If the issuer's format is \SAML2\Constants::NAMEID_ENTITY, this property will just take the issuer's string
63
     * value.
64
     *
65
     * @var \SimpleSAML\SAML2\XML\saml\Issuer
66
     */
67
    protected Issuer $issuer;
68
69
    /**
70
     * The subject of this assertion
71
     *
72
     * @var \SimpleSAML\SAML2\XML\saml\Subject|null
73
     */
74
    protected ?Subject $subject;
75
76
    /**
77
     * The subject of this assertion
78
     *
79
     * If the NameId is null, no subject was included in the assertion.
80
     *
81
     * @var \SimpleSAML\SAML2\XML\saml\NameID|null
82
     */
83
    protected ?NameID $nameId = null;
84
85
    /**
86
     * The encrypted NameId of the subject.
87
     *
88
     * If this is not null, the NameId needs decryption before it can be accessed.
89
     *
90
     * @var \DOMElement|null
91
     */
92
    protected ?DOMElement $encryptedNameId = null;
93
94
    /**
95
     * The statements made by this assertion.
96
     *
97
     * @var \SimpleSAML\SAML2\XML\saml\AbstractStatement[]
98
     */
99
    protected array $statements = [];
100
101
    /**
102
     * The attributes, as an associative array, indexed by attribute name
103
     *
104
     * To ease handling, all attribute values are represented as an array of values, also for values with a multiplicity
105
     * of single. There are 5 possible variants of datatypes for the values: a string, an integer, an array, a
106
     * DOMNodeList or a \SimpleSAML\SAML2\XML\saml\NameID object.
107
     *
108
     * If the attribute is an eduPersonTargetedID, the values will be SAML2\XML\saml\NameID objects.
109
     * If the attribute value has an type-definition (xsi:string or xsi:int), the values will be of that type.
110
     * If the attribute value contains a nested XML structure, the values will be a DOMNodeList
111
     * In all other cases the values are treated as strings
112
     *
113
     * **WARNING** a DOMNodeList cannot be serialized without data-loss and should be handled explicitly
114
     *
115
     * @var array multi-dimensional array of \DOMNodeList|\SimpleSAML\SAML2\XML\saml\NameID|string|int|array
116
     */
117
    protected array $attributes = [];
118
119
    /**
120
     * The SubjectConfirmation elements of the Subject in the assertion.
121
     *
122
     * @var \SimpleSAML\SAML2\XML\saml\SubjectConfirmation[]
123
     */
124
    protected array $SubjectConfirmation = [];
125
126
    /**
127
     * @var bool
128
     */
129
    protected bool $wasSignedAtConstruction = false;
130
131
    /**
132
     * @var \SimpleSAML\SAML2\XML\saml\Conditions|null
133
     */
134
    protected $conditions;
135
136
137
    /**
138
     * Assertion constructor.
139
     *
140
     * @param \SimpleSAML\SAML2\XML\saml\Issuer $issuer
141
     * @param string|null $id
142
     * @param int|null $issueInstant
143
     * @param \SimpleSAML\SAML2\XML\saml\Subject|null $subject
144
     * @param \SimpleSAML\SAML2\XML\saml\Conditions|null $conditions
145
     * @param \SimpleSAML\SAML2\XML\saml\AbstractStatement[] $statements
146
     */
147
    public function __construct(
148
        Issuer $issuer,
149
        ?string $id = null,
150
        ?int $issueInstant = null,
151
        ?Subject $subject = null,
152
        ?Conditions $conditions = null,
153
        array $statements = []
154
    ) {
155
        Assert::true(
156
            $subject || !empty($statements),
157
            "Either a <saml:Subject> or some statement must be present in a <saml:Assertion>"
158
        );
159
        $this->setIssuer($issuer);
160
        $this->setId($id);
161
        $this->setIssueInstant($issueInstant);
162
        $this->setSubject($subject);
163
        $this->setConditions($conditions);
164
        $this->setStatements($statements);
165
    }
166
167
168
    /**
169
     * Collect the value of the subject
170
     *
171
     * @return \SimpleSAML\SAML2\XML\saml\Subject|null
172
     */
173
    public function getSubject(): ?Subject
174
    {
175
        return $this->subject;
176
    }
177
178
179
    /**
180
     * Set the value of the subject-property
181
     *
182
     * @param \SimpleSAML\SAML2\XML\saml\Subject|null $subject
183
     */
184
    protected function setSubject(?Subject $subject): void
185
    {
186
        $this->subject = $subject;
187
    }
188
189
190
    /**
191
     * Collect the value of the conditions-property
192
     *
193
     * @return \SimpleSAML\SAML2\XML\saml\Conditions|null
194
     */
195
    public function getConditions(): ?Conditions
196
    {
197
        return $this->conditions;
198
    }
199
200
201
    /**
202
     * Set the value of the conditions-property
203
     *
204
     * @param \SimpleSAML\SAML2\XML\saml\Conditions|null $conditions
205
     */
206
    protected function setConditions(?Conditions $conditions): void
207
    {
208
        $this->conditions = $conditions;
209
    }
210
211
212
    /**
213
     * @return \SimpleSAML\SAML2\XML\saml\AttributeStatement[]
214
     */
215
    public function getAttributeStatements(): array
216
    {
217
        return array_values(array_filter($this->statements, function ($statement) {
218
            return $statement instanceof AttributeStatement;
219
        }));
220
    }
221
222
223
    /**
224
     * @return \SimpleSAML\SAML2\XML\saml\AuthnStatement[]
225
     */
226
    public function getAuthnStatements(): array
227
    {
228
        return array_values(array_filter($this->statements, function ($statement) {
229
            return $statement instanceof AuthnStatement;
230
        }));
231
    }
232
233
234
    /**
235
     * @return \SimpleSAML\SAML2\XML\saml\Statement[]
236
     */
237
    public function getStatements(): array
238
    {
239
        return array_values(array_filter($this->statements, function ($statement) {
240
            return $statement instanceof Statement;
241
        }));
242
    }
243
244
245
    /**
246
     * Set the statements in this assertion
247
     *
248
     * @param \SimpleSAML\SAML2\XML\saml\AbstractStatement[] $statements
249
     */
250
    protected function setStatements(array $statements): void
251
    {
252
        Assert::allIsInstanceOf($statements, AbstractStatement::class);
253
254
        $this->statements = $statements;
255
    }
256
257
258
    /**
259
     * Retrieve the identifier of this assertion.
260
     *
261
     * @return string The identifier of this assertion.
262
     */
263
    public function getId(): string
264
    {
265
        return $this->id;
266
    }
267
268
269
    /**
270
     * Set the identifier of this assertion.
271
     *
272
     * @param string|null $id The new identifier of this assertion.
273
     */
274
    public function setId(?string $id): void
275
    {
276
        Assert::nullOrNotWhitespaceOnly($id);
277
278
        if ($id === null) {
279
            $id = Utils::getContainer()->generateId();
280
        }
281
        $this->id = $id;
282
    }
283
284
285
    /**
286
     * Retrieve the issue timestamp of this assertion.
287
     *
288
     * @return int The issue timestamp of this assertion, as an UNIX timestamp.
289
     */
290
    public function getIssueInstant(): int
291
    {
292
        return $this->issueInstant;
293
    }
294
295
296
    /**
297
     * Set the issue timestamp of this assertion.
298
     *
299
     * @param int|null $issueInstant The new issue timestamp of this assertion, as an UNIX timestamp.
300
     */
301
    public function setIssueInstant(?int $issueInstant): void
302
    {
303
        if ($issueInstant === null) {
304
            $issueInstant = Temporal::getTime();
305
        }
306
307
        $this->issueInstant = $issueInstant;
308
    }
309
310
311
    /**
312
     * Retrieve the issuer if this assertion.
313
     *
314
     * @return \SimpleSAML\SAML2\XML\saml\Issuer The issuer of this assertion.
315
     */
316
    public function getIssuer(): Issuer
317
    {
318
        return $this->issuer;
319
    }
320
321
322
    /**
323
     * Set the issuer of this message.
324
     *
325
     * @param \SimpleSAML\SAML2\XML\saml\Issuer $issuer The new issuer of this assertion.
326
     */
327
    public function setIssuer(Issuer $issuer): void
328
    {
329
        $this->issuer = $issuer;
330
    }
331
332
333
    /**
334
     * Retrieve the SubjectConfirmation elements we have in our Subject element.
335
     *
336
     * @return array Array of \SimpleSAML\SAML2\XML\saml\SubjectConfirmation elements.
337
     */
338
    public function getSubjectConfirmation(): array
339
    {
340
        return $this->SubjectConfirmation;
341
    }
342
343
344
    /**
345
     * Set the SubjectConfirmation elements that should be included in the assertion.
346
     *
347
     * @param array $SubjectConfirmation Array of \SimpleSAML\SAML2\XML\saml\SubjectConfirmation elements.
348
     * @throws \SimpleSAML\Assert\AssertionFailedException if assertions are false
349
     */
350
    public function setSubjectConfirmation(array $SubjectConfirmation): void
351
    {
352
        Assert::allIsInstanceOf($SubjectConfirmation, SubjectConfirmation::class);
353
        $this->SubjectConfirmation = $SubjectConfirmation;
354
    }
355
356
357
    /**
358
     * @return bool
359
     */
360
    public function wasSignedAtConstruction(): bool
361
    {
362
        return $this->wasSignedAtConstruction;
363
    }
364
365
366
    /**
367
     * Get the XML element.
368
     *
369
     * @return \DOMElement
370
     */
371
    public function getXML(): DOMElement
372
    {
373
        return $this->xml;
374
    }
375
376
377
    /**
378
     * Set the XML element.
379
     *
380
     * @param \DOMElement $xml
381
     */
382
    private function setXML(DOMElement $xml): void
0 ignored issues
show
Unused Code introduced by
The method setXML() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
383
    {
384
        $this->xml = $xml;
0 ignored issues
show
Bug Best Practice introduced by
The property xml does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
385
    }
386
387
388
    /**
389
     * @inheritDoc
390
     */
391
    protected function getOriginalXML(): DOMElement
392
    {
393
        return $this->xml ?? $this->toUnsignedXML();
394
    }
395
396
397
    /**
398
     * Convert XML into an Assertion
399
     *
400
     * @param \DOMElement $xml The XML element we should load
401
     * @return \SimpleSAML\SAML2\XML\saml\Assertion
402
     *
403
     * @throws \SimpleSAML\Assert\AssertionFailedException if assertions are false
404
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException if the qualified name of the supplied element is wrong
405
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException if the supplied element is missing one of the mandatory attributes
406
     * @throws \SimpleSAML\XML\Exception\MissingElementException if one of the mandatory child-elements is missing
407
     * @throws \SimpleSAML\XML\Exception\TooManyElementsException if too many child-elements of a type are specified
408
     * @throws \Exception
409
     */
410
    public static function fromXML(DOMElement $xml): object
411
    {
412
        Assert::same($xml->localName, 'Assertion', InvalidDOMElementException::class);
413
        Assert::same($xml->namespaceURI, Assertion::NS, InvalidDOMElementException::class);
414
        Assert::same(self::getAttribute($xml, 'Version'), '2.0', 'Unsupported version: %s');
415
416
        $issueInstant = self::getAttribute($xml, 'IssueInstant');
417
        Assert::validDateTimeZulu($issueInstant, ProtocolViolationException::class);
418
        $issueInstant = XMLUtils::xsDateTimeToTimestamp($issueInstant);
419
420
        $issuer = Issuer::getChildrenOfClass($xml);
421
        Assert::minCount($issuer, 1, 'Missing <saml:Issuer> in assertion.', MissingElementException::class);
422
        Assert::maxCount($issuer, 1, 'More than one <saml:Issuer> in assertion.', TooManyElementsException::class);
423
424
        $subject = Subject::getChildrenOfClass($xml);
425
        Assert::maxCount($subject, 1, 'More than one <saml:Subject> in <saml:Assertion>', TooManyElementsException::class);
426
427
        $conditions = Conditions::getChildrenOfClass($xml);
428
        Assert::maxCount($conditions, 1, 'More than one <saml:Conditions> in <saml:Assertion>.', TooManyElementsException::class);
429
430
        $signature = Signature::getChildrenOfClass($xml);
431
        Assert::maxCount($signature, 1, 'Only one <ds:Signature> element is allowed.', TooManyElementsException::class);
432
433
        $authnStatement = AuthnStatement::getChildrenOfClass($xml);
434
        $attrStatement = AttributeStatement::getChildrenOfClass($xml);
435
        $statements = Statement::getChildrenOfClass($xml);
436
437
        $assertion = new self(
438
            array_pop($issuer),
439
            self::getAttribute($xml, 'ID'),
440
            $issueInstant,
441
            array_pop($subject),
442
            array_pop($conditions),
443
            array_merge($authnStatement, $attrStatement, $statements)
444
        );
445
446
        if (!empty($signature)) {
447
            $assertion->setSignature($signature[0]);
448
            $assertion->wasSignedAtConstruction = true;
449
        }
450
451
        return $assertion;
452
    }
453
454
455
    /**
456
     * Convert this assertion to an unsigned XML document.
457
     * This method does not sign the resulting XML document.
458
     *
459
     * @return \DOMElement The root element of the DOM tree
460
     */
461
    protected function toUnsignedXML(?DOMElement $parent = null): DOMElement
462
    {
463
        $e = $this->instantiateParentElement($parent);
464
465
        $e->setAttribute('Version', '2.0');
466
        $e->setAttribute('ID', $this->id);
467
        $e->setAttribute('IssueInstant', gmdate('Y-m-d\TH:i:s\Z', $this->issueInstant));
468
469
        $issuer = $this->issuer->toXML($e);
0 ignored issues
show
Unused Code introduced by
The assignment to $issuer is dead and can be removed.
Loading history...
470
471
        if ($this->subject !== null) {
472
            $this->subject->toXML($e);
473
        }
474
475
        if ($this->conditions !== null) {
476
            $this->conditions->toXML($e);
477
        }
478
479
        foreach ($this->statements as $statement) {
480
            $statement->toXML($e);
481
        }
482
483
        return $e;
484
    }
485
486
487
    /**
488
     * Convert this assertion to a signed XML element, if a signer was set.
489
     *
490
     * @param \DOMElement|null $parent The DOM node the assertion should be created in.
491
     *
492
     * @return \DOMElement This assertion.
493
     * @throws \Exception
494
     */
495
    public function toXML(DOMElement $parent = null): DOMElement
496
    {
497
        $e = $this->toUnsignedXML($parent);
498
499
        if ($this->signer !== null) {
500
            $signedXML = $this->doSign($e);
501
502
            // Test for an Issuer
503
            $messageElements = XPath::xpQuery($signedXML, './saml_assertion:Issuer', XPath::getXPath($signedXML));
504
            $issuer = array_pop($messageElements);
505
506
            $signedXML->insertBefore($this->signature->toXML($signedXML), $issuer->nextSibling);
507
            return $signedXML;
508
        }
509
510
        return $e;
511
    }
512
}
513