Passed
Pull Request — master (#280)
by Tim
02:43 queued 22s
created

Assertion::getOriginalXML()   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 0
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;
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;
1 ignored issue
show
introduced by
The trait SimpleSAML\XMLSecurity\XML\SignableElementTrait requires some properties which are not provided by SimpleSAML\SAML2\XML\saml\Assertion: $ownerDocument, $documentElement
Loading history...
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
    /** @var \DOMElement $xml */
53
    protected DOMElement $xml;
54
55
    /**
56
     * The issue timestamp of this assertion, as an UNIX timestamp.
57
     *
58
     * @var int
59
     */
60
    protected int $issueInstant;
61
62
    /**
63
     * The issuer of this assertion.
64
     *
65
     * If the issuer's format is \SAML2\Constants::NAMEID_ENTITY, this property will just take the issuer's string
66
     * value.
67
     *
68
     * @var \SimpleSAML\SAML2\XML\saml\Issuer
69
     */
70
    protected Issuer $issuer;
71
72
    /**
73
     * The subject of this assertion
74
     *
75
     * @var \SimpleSAML\SAML2\XML\saml\Subject|null
76
     */
77
    protected ?Subject $subject;
78
79
    /**
80
     * The subject of this assertion
81
     *
82
     * If the NameId is null, no subject was included in the assertion.
83
     *
84
     * @var \SimpleSAML\SAML2\XML\saml\NameID|null
85
     */
86
    protected ?NameID $nameId = null;
87
88
    /**
89
     * The encrypted NameId of the subject.
90
     *
91
     * If this is not null, the NameId needs decryption before it can be accessed.
92
     *
93
     * @var \DOMElement|null
94
     */
95
    protected ?DOMElement $encryptedNameId = null;
96
97
    /**
98
     * The statements made by this assertion.
99
     *
100
     * @var \SimpleSAML\SAML2\XML\saml\AbstractStatement[]
101
     */
102
    protected $statements = [];
103
104
    /**
105
     * The attributes, as an associative array, indexed by attribute name
106
     *
107
     * To ease handling, all attribute values are represented as an array of values, also for values with a multiplicity
108
     * of single. There are 5 possible variants of datatypes for the values: a string, an integer, an array, a
109
     * DOMNodeList or a \SimpleSAML\SAML2\XML\saml\NameID object.
110
     *
111
     * If the attribute is an eduPersonTargetedID, the values will be SAML2\XML\saml\NameID objects.
112
     * If the attribute value has an type-definition (xsi:string or xsi:int), the values will be of that type.
113
     * If the attribute value contains a nested XML structure, the values will be a DOMNodeList
114
     * In all other cases the values are treated as strings
115
     *
116
     * **WARNING** a DOMNodeList cannot be serialized without data-loss and should be handled explicitly
117
     *
118
     * @var array multi-dimensional array of \DOMNodeList|\SimpleSAML\SAML2\XML\saml\NameID|string|int|array
119
     */
120
    protected array $attributes = [];
121
122
    /**
123
     * The SubjectConfirmation elements of the Subject in the assertion.
124
     *
125
     * @var \SimpleSAML\SAML2\XML\saml\SubjectConfirmation[]
126
     */
127
    protected array $SubjectConfirmation = [];
128
129
    /**
130
     * @var bool
131
     */
132
    protected bool $wasSignedAtConstruction = false;
133
134
    /**
135
     * @var \SimpleSAML\SAML2\XML\saml\Conditions|null
136
     */
137
    protected $conditions;
138
139
140
    /**
141
     * Assertion constructor.
142
     *
143
     * @param \SimpleSAML\SAML2\XML\saml\Issuer $issuer
144
     * @param string|null $id
145
     * @param int|null $issueInstant
146
     * @param \SimpleSAML\SAML2\XML\saml\Subject|null $subject
147
     * @param \SimpleSAML\SAML2\XML\saml\Conditions|null $conditions
148
     * @param \SimpleSAML\SAML2\XML\saml\AbstractStatement[] $statements
149
     */
150
    public function __construct(
151
        Issuer $issuer,
152
        ?string $id = null,
153
        ?int $issueInstant = null,
154
        ?Subject $subject = null,
155
        ?Conditions $conditions = null,
156
        array $statements = []
157
    ) {
158
        Assert::true(
159
            $subject || !empty($statements),
160
            "Either a <saml:Subject> or some statement must be present in a <saml:Assertion>"
161
        );
162
        $this->setIssuer($issuer);
163
        $this->setId($id);
164
        $this->setIssueInstant($issueInstant);
165
        $this->setSubject($subject);
166
        $this->setConditions($conditions);
167
        $this->setStatements($statements);
168
    }
169
170
171
    /**
172
     * Collect the value of the subject
173
     *
174
     * @return \SimpleSAML\SAML2\XML\saml\Subject|null
175
     */
176
    public function getSubject(): ?Subject
177
    {
178
        return $this->subject;
179
    }
180
181
182
    /**
183
     * Set the value of the subject-property
184
     *
185
     * @param \SimpleSAML\SAML2\XML\saml\Subject|null $subject
186
     */
187
    protected function setSubject(?Subject $subject): void
188
    {
189
        $this->subject = $subject;
190
    }
191
192
193
    /**
194
     * Collect the value of the conditions-property
195
     *
196
     * @return \SimpleSAML\SAML2\XML\saml\Conditions|null
197
     */
198
    public function getConditions(): ?Conditions
199
    {
200
        return $this->conditions;
201
    }
202
203
204
    /**
205
     * Set the value of the conditions-property
206
     *
207
     * @param \SimpleSAML\SAML2\XML\saml\Conditions|null $conditions
208
     */
209
    protected function setConditions(?Conditions $conditions): void
210
    {
211
        $this->conditions = $conditions;
212
    }
213
214
215
    /**
216
     * @return \SimpleSAML\SAML2\XML\saml\AttributeStatement[]
217
     */
218
    public function getAttributeStatements(): array
219
    {
220
        return array_values(array_filter($this->statements, function ($statement) {
221
            return $statement instanceof AttributeStatement;
222
        }));
223
    }
224
225
226
    /**
227
     * @return \SimpleSAML\SAML2\XML\saml\AuthnStatement[]
228
     */
229
    public function getAuthnStatements(): array
230
    {
231
        return array_values(array_filter($this->statements, function ($statement) {
232
            return $statement instanceof AuthnStatement;
233
        }));
234
    }
235
236
237
    /**
238
     * @return \SimpleSAML\SAML2\XML\saml\Statement[]
239
     */
240
    public function getStatements(): array
241
    {
242
        return array_values(array_filter($this->statements, function ($statement) {
243
            return $statement instanceof Statement;
244
        }));
245
    }
246
247
248
    /**
249
     * Set the statements in this assertion
250
     *
251
     * @param \SimpleSAML\SAML2\XML\saml\AbstractStatement[] $statements
252
     */
253
    protected function setStatements(array $statements): void
254
    {
255
        Assert::allIsInstanceOf($statements, AbstractStatement::class);
256
257
        $this->statements = $statements;
258
    }
259
260
261
    /**
262
     * Retrieve the identifier of this assertion.
263
     *
264
     * @return string The identifier of this assertion.
265
     */
266
    public function getId(): string
267
    {
268
        return $this->id;
269
    }
270
271
272
    /**
273
     * Set the identifier of this assertion.
274
     *
275
     * @param string|null $id The new identifier of this assertion.
276
     */
277
    public function setId(?string $id): void
278
    {
279
        Assert::nullOrNotWhitespaceOnly($id);
280
281
        if ($id === null) {
282
            $id = Utils::getContainer()->generateId();
283
        }
284
        $this->id = $id;
285
    }
286
287
288
    /**
289
     * Retrieve the issue timestamp of this assertion.
290
     *
291
     * @return int The issue timestamp of this assertion, as an UNIX timestamp.
292
     */
293
    public function getIssueInstant(): int
294
    {
295
        return $this->issueInstant;
296
    }
297
298
299
    /**
300
     * Set the issue timestamp of this assertion.
301
     *
302
     * @param int|null $issueInstant The new issue timestamp of this assertion, as an UNIX timestamp.
303
     */
304
    public function setIssueInstant(?int $issueInstant): void
305
    {
306
        if ($issueInstant === null) {
307
            $issueInstant = Temporal::getTime();
308
        }
309
310
        $this->issueInstant = $issueInstant;
311
    }
312
313
314
    /**
315
     * Retrieve the issuer if this assertion.
316
     *
317
     * @return \SimpleSAML\SAML2\XML\saml\Issuer The issuer of this assertion.
318
     */
319
    public function getIssuer(): Issuer
320
    {
321
        return $this->issuer;
322
    }
323
324
325
    /**
326
     * Set the issuer of this message.
327
     *
328
     * @param \SimpleSAML\SAML2\XML\saml\Issuer $issuer The new issuer of this assertion.
329
     */
330
    public function setIssuer(Issuer $issuer): void
331
    {
332
        $this->issuer = $issuer;
333
    }
334
335
336
    /**
337
     * Retrieve the SubjectConfirmation elements we have in our Subject element.
338
     *
339
     * @return array Array of \SimpleSAML\SAML2\XML\saml\SubjectConfirmation elements.
340
     */
341
    public function getSubjectConfirmation(): array
342
    {
343
        return $this->SubjectConfirmation;
344
    }
345
346
347
    /**
348
     * Set the SubjectConfirmation elements that should be included in the assertion.
349
     *
350
     * @param array $SubjectConfirmation Array of \SimpleSAML\SAML2\XML\saml\SubjectConfirmation elements.
351
     * @throws \SimpleSAML\Assert\AssertionFailedException if assertions are false
352
     */
353
    public function setSubjectConfirmation(array $SubjectConfirmation): void
354
    {
355
        Assert::allIsInstanceOf($SubjectConfirmation, SubjectConfirmation::class);
356
        $this->SubjectConfirmation = $SubjectConfirmation;
357
    }
358
359
360
    /**
361
     * @return bool
362
     */
363
    public function wasSignedAtConstruction(): bool
364
    {
365
        return $this->wasSignedAtConstruction;
366
    }
367
368
369
    /**
370
     * Get the XML element.
371
     *
372
     * @return \DOMElement
373
     */
374
    public function getXML(): DOMElement
375
    {
376
        return $this->xml;
377
    }
378
379
380
    /**
381
     * Set the XML element.
382
     *
383
     * @param \DOMElement $xml
384
     */
385
    private function setXML(DOMElement $xml): void
386
    {
387
        $this->xml = $xml;
388
    }
389
390
391
    /**
392
     * @inheritDoc
393
     */
394
    protected function getOriginalXML(): DOMElement
395
    {
396
        return $this->xml;
397
    }
398
399
400
    /**
401
     * Convert XML into an Assertion
402
     *
403
     * @param \DOMElement $xml The XML element we should load
404
     * @return \SimpleSAML\SAML2\XML\saml\Assertion
405
     *
406
     * @throws \SimpleSAML\Assert\AssertionFailedException if assertions are false
407
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException if the qualified name of the supplied element is wrong
408
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException if the supplied element is missing one of the mandatory attributes
409
     * @throws \SimpleSAML\XML\Exception\MissingElementException if one of the mandatory child-elements is missing
410
     * @throws \SimpleSAML\XML\Exception\TooManyElementsException if too many child-elements of a type are specified
411
     * @throws \Exception
412
     */
413
    public static function fromXML(DOMElement $xml): object
414
    {
415
        Assert::same($xml->localName, 'Assertion', InvalidDOMElementException::class);
416
        Assert::same($xml->namespaceURI, Assertion::NS, InvalidDOMElementException::class);
417
        Assert::same(self::getAttribute($xml, 'Version'), '2.0', 'Unsupported version: %s');
418
419
        $issueInstant = self::getAttribute($xml, 'IssueInstant');
420
        Assert::validDateTimeZulu($issueInstant, ProtocolViolationException::class);
421
        $issueInstant = XMLUtils::xsDateTimeToTimestamp($issueInstant);
422
423
        $issuer = Issuer::getChildrenOfClass($xml);
424
        Assert::minCount($issuer, 1, 'Missing <saml:Issuer> in assertion.', MissingElementException::class);
425
        Assert::maxCount($issuer, 1, 'More than one <saml:Issuer> in assertion.', TooManyElementsException::class);
426
427
        $subject = Subject::getChildrenOfClass($xml);
428
        Assert::maxCount($subject, 1, 'More than one <saml:Subject> in <saml:Assertion>', TooManyElementsException::class);
429
430
        $conditions = Conditions::getChildrenOfClass($xml);
431
        Assert::maxCount($conditions, 1, 'More than one <saml:Conditions> in <saml:Assertion>.', TooManyElementsException::class);
432
433
        $signature = Signature::getChildrenOfClass($xml);
434
        Assert::maxCount($signature, 1, 'Only one <ds:Signature> element is allowed.', TooManyElementsException::class);
435
436
        $authnStatement = AuthnStatement::getChildrenOfClass($xml);
437
        $attrStatement = AttributeStatement::getChildrenOfClass($xml);
438
        $statements = Statement::getChildrenOfClass($xml);
439
440
        $assertion = new self(
441
            array_pop($issuer),
442
            self::getAttribute($xml, 'ID'),
443
            $issueInstant,
444
            array_pop($subject),
445
            array_pop($conditions),
446
            array_merge($authnStatement, $attrStatement, $statements)
447
        );
448
449
        if (!empty($signature)) {
450
            $assertion->setSignature($signature[0]);
451
            $assertion->wasSignedAtConstruction = true;
452
        }
453
454
        $assertion->setXML($xml);
455
        return $assertion;
456
    }
457
458
459
    /**
460
     * Convert this assertion to an XML element.
461
     *
462
     * @param \DOMElement|null $parentElement The DOM node the assertion should be created in.
463
     *
464
     * @return \DOMElement This assertion.
465
     * @throws \Exception
466
     */
467
    public function toXML(DOMElement $parentElement = null): DOMElement
468
    {
469
        $e = $this->instantiateParentElement($parentElement);
470
471
        $e->setAttribute('Version', '2.0');
472
        $e->setAttribute('ID', $this->id);
473
        $e->setAttribute('IssueInstant', gmdate('Y-m-d\TH:i:s\Z', $this->issueInstant));
474
475
        $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...
476
477
        if ($this->subject !== null) {
478
            $this->subject->toXML($e);
479
        }
480
481
        if ($this->conditions !== null) {
482
            $this->conditions->toXML($e);
483
        }
484
485
        foreach ($this->statements as $statement) {
486
            $statement->toXML($e);
487
        }
488
489
        if ($this->signer !== null) {
490
            $signedXML = $this->doSign($e);
491
492
            // Test for an Issuer
493
            $assertionElements = XPath::xpQuery($signedXML, './saml_assertion:Issuer', XPath::getXPath($signedXML));
494
            $issuer = array_pop($assertionElements);
495
496
            $signedXML->insertBefore($this->signature->toXML($signedXML), $issuer->nextSibling);
497
            return $signedXML;
498
        }
499
500
        return $e;
501
    }
502
}
503