Passed
Pull Request — master (#280)
by Tim
02:52
created

Assertion::setIssueInstant()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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