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

Assertion   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 432
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 95
dl 0
loc 432
rs 10
c 0
b 0
f 0
wmc 29

21 Methods

Rating   Name   Duplication   Size   Complexity  
A setSubjectConfirmation() 0 4 1
A fromXML() 0 43 2
A getOriginalXML() 0 3 1
A setId() 0 8 2
A setSubject() 0 3 1
A toXML() 0 27 5
A setIssueInstant() 0 7 2
A getStatements() 0 4 1
A getSubject() 0 3 1
A setStatements() 0 5 1
A setIssuer() 0 3 1
A getConditions() 0 3 1
A getAuthnStatements() 0 4 1
A getIssuer() 0 3 1
A __construct() 0 18 2
A getId() 0 3 1
A wasSignedAtConstruction() 0 3 1
A getIssueInstant() 0 3 1
A getAttributeStatements() 0 4 1
A setConditions() 0 3 1
A getSubjectConfirmation() 0 3 1
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
     * @inheritDoc
370
     */
371
    protected function getOriginalXML(): DOMElement
372
    {
373
        return $this->xml;
374
    }
375
376
377
    /**
378
     * Convert XML into an Assertion
379
     *
380
     * @param \DOMElement $xml The XML element we should load
381
     * @return \SimpleSAML\SAML2\XML\saml\Assertion
382
     *
383
     * @throws \SimpleSAML\Assert\AssertionFailedException if assertions are false
384
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException if the qualified name of the supplied element is wrong
385
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException if the supplied element is missing one of the mandatory attributes
386
     * @throws \SimpleSAML\XML\Exception\MissingElementException if one of the mandatory child-elements is missing
387
     * @throws \SimpleSAML\XML\Exception\TooManyElementsException if too many child-elements of a type are specified
388
     * @throws \Exception
389
     */
390
    public static function fromXML(DOMElement $xml): object
391
    {
392
        Assert::same($xml->localName, 'Assertion', InvalidDOMElementException::class);
393
        Assert::same($xml->namespaceURI, Assertion::NS, InvalidDOMElementException::class);
394
        Assert::same(self::getAttribute($xml, 'Version'), '2.0', 'Unsupported version: %s');
395
396
        $issueInstant = self::getAttribute($xml, 'IssueInstant');
397
        Assert::validDateTimeZulu($issueInstant, ProtocolViolationException::class);
398
        $issueInstant = XMLUtils::xsDateTimeToTimestamp($issueInstant);
399
400
        $issuer = Issuer::getChildrenOfClass($xml);
401
        Assert::minCount($issuer, 1, 'Missing <saml:Issuer> in assertion.', MissingElementException::class);
402
        Assert::maxCount($issuer, 1, 'More than one <saml:Issuer> in assertion.', TooManyElementsException::class);
403
404
        $subject = Subject::getChildrenOfClass($xml);
405
        Assert::maxCount($subject, 1, 'More than one <saml:Subject> in <saml:Assertion>', TooManyElementsException::class);
406
407
        $conditions = Conditions::getChildrenOfClass($xml);
408
        Assert::maxCount($conditions, 1, 'More than one <saml:Conditions> in <saml:Assertion>.', TooManyElementsException::class);
409
410
        $signature = Signature::getChildrenOfClass($xml);
411
        Assert::maxCount($signature, 1, 'Only one <ds:Signature> element is allowed.', TooManyElementsException::class);
412
413
        $authnStatement = AuthnStatement::getChildrenOfClass($xml);
414
        $attrStatement = AttributeStatement::getChildrenOfClass($xml);
415
        $statements = Statement::getChildrenOfClass($xml);
416
417
        $assertion = new self(
418
            array_pop($issuer),
419
            self::getAttribute($xml, 'ID'),
420
            $issueInstant,
421
            array_pop($subject),
422
            array_pop($conditions),
423
            array_merge($authnStatement, $attrStatement, $statements)
424
        );
425
426
        if (!empty($signature)) {
427
            $assertion->setSignature($signature[0]);
428
            $assertion->wasSignedAtConstruction = true;
429
        }
430
431
        $assertion->setXML($xml);
0 ignored issues
show
Bug introduced by
The method setXML() does not exist on SimpleSAML\SAML2\XML\saml\Assertion. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

431
        $assertion->/** @scrutinizer ignore-call */ 
432
                    setXML($xml);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
432
        return $assertion;
433
    }
434
435
436
    /**
437
     * Convert this assertion to an XML element.
438
     *
439
     * @param \DOMElement|null $parentElement The DOM node the assertion should be created in.
440
     *
441
     * @return \DOMElement This assertion.
442
     * @throws \Exception
443
     */
444
    public function toXML(DOMElement $parentElement = null): DOMElement
445
    {
446
        $e = $this->instantiateParentElement($parentElement);
447
448
        $e->setAttribute('Version', '2.0');
449
        $e->setAttribute('ID', $this->id);
450
        $e->setAttribute('IssueInstant', gmdate('Y-m-d\TH:i:s\Z', $this->issueInstant));
451
452
        $issuer = $this->issuer->toXML($e);
453
454
        if ($this->subject !== null) {
455
            $this->subject->toXML($e);
456
        }
457
458
        if ($this->conditions !== null) {
459
            $this->conditions->toXML($e);
460
        }
461
462
        foreach ($this->statements as $statement) {
463
            $statement->toXML($e);
464
        }
465
466
        if ($this->signingKey !== null) {
0 ignored issues
show
Bug Best Practice introduced by
The property signingKey does not exist on SimpleSAML\SAML2\XML\saml\Assertion. Did you maybe forget to declare it?
Loading history...
467
            SecurityUtils::insertSignature($this->signingKey, $this->certificates, $e, $issuer->nextSibling);
0 ignored issues
show
Bug Best Practice introduced by
The property certificates does not exist on SimpleSAML\SAML2\XML\saml\Assertion. Did you maybe forget to declare it?
Loading history...
Bug introduced by
The method insertSignature() does not exist on SimpleSAML\XMLSecurity\Utils\Security. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

467
            SecurityUtils::/** @scrutinizer ignore-call */ 
468
                           insertSignature($this->signingKey, $this->certificates, $e, $issuer->nextSibling);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
468
        }
469
470
        return $e;
471
    }
472
}
473