Completed
Push — master ( f8013d...9b3e48 )
by Thijs
06:13
created

Assertion::setNotBefore()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SAML2;
4
5
use RobRichards\XMLSecLibs\XMLSecEnc;
6
use RobRichards\XMLSecLibs\XMLSecurityKey;
7
use SAML2\Exception\RuntimeException;
8
use SAML2\Utilities\Temporal;
9
use SAML2\XML\Chunk;
10
use SAML2\XML\saml\SubjectConfirmation;
11
12
/**
13
 * Class representing a SAML 2 assertion.
14
 *
15
 * @package SimpleSAMLphp
16
 */
17
class Assertion implements SignedElement
18
{
19
    /**
20
     * The identifier of this assertion.
21
     *
22
     * @var string
23
     */
24
    private $id;
25
26
    /**
27
     * The issue timestamp of this assertion, as an UNIX timestamp.
28
     *
29
     * @var int
30
     */
31
    private $issueInstant;
32
33
    /**
34
     * The issuer of this assertion.
35
     *
36
     * If the issuer's format is \SAML2\Constants::NAMEID_ENTITY, this property will just take the issuer's string
37
     * value.
38
     *
39
     * @var string|\SAML2\XML\saml\Issuer
40
     */
41
    private $issuer;
42
43
    /**
44
     * The NameId of the subject in the assertion.
45
     *
46
     * If the NameId is null, no subject was included in the assertion.
47
     *
48
     * @var \SAML2\XML\saml\NameID|null
49
     */
50
    private $nameId;
51
52
    /**
53
     * The encrypted NameId of the subject.
54
     *
55
     * If this is not null, the NameId needs decryption before it can be accessed.
56
     *
57
     * @var \DOMElement|null
58
     */
59
    private $encryptedNameId;
60
61
    /**
62
     * The encrypted Attributes.
63
     *
64
     * If this is not an empty array, these Attributes need decryption before they can be used.
65
     *
66
     * @var \DOMElement[]
67
     */
68
    private $encryptedAttributes;
69
70
    /**
71
     * Private key we should use to encrypt the attributes.
72
     *
73
     * @var XMLSecurityKey|null
74
     */
75
    private $encryptionKey;
76
77
     /**
78
     * The earliest time this assertion is valid, as an UNIX timestamp.
79
     *
80
     * @var int
81
     */
82
    private $notBefore;
83
84
    /**
85
     * The time this assertion expires, as an UNIX timestamp.
86
     *
87
     * @var int
88
     */
89
    private $notOnOrAfter;
90
91
    /**
92
     * The set of audiences that are allowed to receive this assertion.
93
     *
94
     * This is an array of valid service providers.
95
     *
96
     * If no restrictions on the audience are present, this variable contains null.
97
     *
98
     * @var array|null
99
     */
100
    private $validAudiences;
101
102
    /**
103
     * The session expiration timestamp.
104
     *
105
     * @var int|null
106
     */
107
    private $sessionNotOnOrAfter;
108
109
    /**
110
     * The session index for this user on the IdP.
111
     *
112
     * Contains null if no session index is present.
113
     *
114
     * @var string|null
115
     */
116
    private $sessionIndex;
117
118
    /**
119
     * The timestamp the user was authenticated, as an UNIX timestamp.
120
     *
121
     * @var int
122
     */
123
    private $authnInstant;
124
125
    /**
126
     * The authentication context reference for this assertion.
127
     *
128
     * @var string|null
129
     */
130
    private $authnContextClassRef;
131
132
    /**
133
     * Authentication context declaration provided by value.
134
     *
135
     * See:
136
     * @url http://docs.oasis-open.org/security/saml/v2.0/saml-authn-context-2.0-os.pdf
137
     *
138
     * @var \SAML2\XML\Chunk
139
     */
140
    private $authnContextDecl;
141
142
    /**
143
     * URI reference that identifies an authentication context declaration.
144
     *
145
     * The URI reference MAY directly resolve into an XML document containing the referenced declaration.
146
     *
147
     * @var \SAML2\XML\Chunk
148
     */
149
    private $authnContextDeclRef;
150
151
    /**
152
     * The list of AuthenticatingAuthorities for this assertion.
153
     *
154
     * @var array
155
     */
156
    private $AuthenticatingAuthority;
157
158
    /**
159
     * The attributes, as an associative array, indexed by attribute name
160
     *
161
     * To ease handling, all attribute values are represented as an array of values, also for values with a multiplicity
162
     * of single. There are 5 possible variants of datatypes for the values: a string, an integer, an array, a
163
     * DOMNodeList or a SAML2\XML\saml\NameID object.
164
     *
165
     * If the attribute is an eduPersonTargetedID, the values will be SAML2\XML\saml\NameID objects.
166
     * If the attribute value has an type-definition (xsi:string or xsi:int), the values will be of that type.
167
     * If the attribute value contains a nested XML structure, the values will be a DOMNodeList
168
     * In all other cases the values are treated as strings
169
     *
170
     * **WARNING** a DOMNodeList cannot be serialized without data-loss and should be handled explicitly
171
     *
172
     * @var array multi-dimensional array of \DOMNodeList|\SAML2\XML\saml\NameID|string|int|array
173
     */
174
    private $attributes;
175
176
    /**
177
     * The attributes values types as per http://www.w3.org/2001/XMLSchema definitions
178
     * the variable is as an associative array, indexed by attribute name
179
     * 
180
     * when parsing assertion, the variable will be:
181
     * - <attribute name> => array(<Value1's xs type>|null, <xs type Value2>|null, ...)
182
     * array will always have the same size of the array of vaules in $attributes for the same <attribute name>
183
     *
184
     * when generating assertion, the varuable can be:
185
     * - null : backward compatibility
186
     * - <attribute name> => <xs type> : all values for the given attribute will have the same xs type
187
     * - <attribute name> => array(<Value1's xs type>|null, <xs type Value2>|null, ...) : Nth value will have type of the Nth in the array
188
     * 
189
     * @var array multi-dimensional array of array
190
     */
191
    private $attributesValueTypes;
192
193
    /**
194
     * The NameFormat used on all attributes.
195
     *
196
     * If more than one NameFormat is used, this will contain
197
     * the unspecified nameformat.
198
     *
199
     * @var string
200
     */
201
    private $nameFormat;
202
203
    /**
204
     * The private key we should use to sign the assertion.
205
     *
206
     * The private key can be null, in which case the assertion is sent unsigned.
207
     *
208
     * @var XMLSecurityKey|null
209
     */
210
    private $signatureKey;
211
212
    /**
213
     * List of certificates that should be included in the assertion.
214
     *
215
     * @var array
216
     */
217
    private $certificates;
218
219
    /**
220
     * The data needed to verify the signature.
221
     *
222
     * @var array|null
223
     */
224
    private $signatureData;
225
226
    /**
227
     * Boolean that indicates if attributes are encrypted in the
228
     * assertion or not.
229
     *
230
     * @var boolean
231
     */
232
    private $requiredEncAttributes;
233
234
    /**
235
     * The SubjectConfirmation elements of the Subject in the assertion.
236
     *
237
     * @var \SAML2\XML\saml\SubjectConfirmation[].
238
     */
239
    private $SubjectConfirmation;
240
241
    /**
242
     * @var bool
243
     */
244
    protected $wasSignedAtConstruction = false;
245
246
    /**
247
     * @var string|null
248
     */
249
    private $signatureMethod;
250
251
    /**
252
     * Constructor for SAML 2 assertions.
253
     *
254
     * @param \DOMElement|null $xml The input assertion.
255
     * @throws \Exception
256
     */
257
    public function __construct(\DOMElement $xml = null)
258
    {
259
        $this->id = Utils::getContainer()->generateId();
260
        $this->issueInstant = Temporal::getTime();
261
        $this->issuer = '';
262
        $this->authnInstant = Temporal::getTime();
263
        $this->attributes = array();
264
        $this->nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
265
        $this->certificates = array();
266
        $this->AuthenticatingAuthority = array();
267
        $this->SubjectConfirmation = array();
268
        $this->requiredEncAttributes = false;
269
270
        if ($xml === null) {
271
            return;
272
        }
273
274
        if (!$xml->hasAttribute('ID')) {
275
            throw new \Exception('Missing ID attribute on SAML assertion.');
276
        }
277
        $this->id = $xml->getAttribute('ID');
278
279
        if ($xml->getAttribute('Version') !== '2.0') {
280
            /* Currently a very strict check. */
281
            throw new \Exception('Unsupported version: ' . $xml->getAttribute('Version'));
282
        }
283
284
        $this->issueInstant = Utils::xsDateTimeToTimestamp($xml->getAttribute('IssueInstant'));
285
286
        $issuer = Utils::xpQuery($xml, './saml_assertion:Issuer');
287
        if (empty($issuer)) {
288
            throw new \Exception('Missing <saml:Issuer> in assertion.');
289
        }
290
        $this->issuer = new XML\saml\Issuer($issuer[0]);
291
        if ($this->issuer->Format === Constants::NAMEID_ENTITY) {
292
            $this->issuer = $this->issuer->value;
293
        }
294
295
        $this->parseSubject($xml);
296
        $this->parseConditions($xml);
297
        $this->parseAuthnStatement($xml);
298
        $this->parseAttributes($xml);
299
        $this->parseEncryptedAttributes($xml);
300
        $this->parseSignature($xml);
301
    }
302
303
    /**
304
     * Parse subject in assertion.
305
     *
306
     * @param \DOMElement $xml The assertion XML element.
307
     * @throws \Exception
308
     */
309
    private function parseSubject(\DOMElement $xml)
310
    {
311
        $subject = Utils::xpQuery($xml, './saml_assertion:Subject');
312
        if (empty($subject)) {
313
            /* No Subject node. */
314
315
            return;
316
        } elseif (count($subject) > 1) {
317
            throw new \Exception('More than one <saml:Subject> in <saml:Assertion>.');
318
        }
319
        $subject = $subject[0];
320
321
        $nameId = Utils::xpQuery(
322
            $subject,
323
            './saml_assertion:NameID | ./saml_assertion:EncryptedID/xenc:EncryptedData'
324
        );
325
        if (count($nameId) > 1) {
326
            throw new \Exception('More than one <saml:NameID> or <saml:EncryptedID> in <saml:Subject>.');
327
        } elseif (!empty($nameId)) {
328
            $nameId = $nameId[0];
329 View Code Duplication
            if ($nameId->localName === 'EncryptedData') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
330
                /* The NameID element is encrypted. */
331
                $this->encryptedNameId = $nameId;
332
            } else {
333
                $this->nameId = new XML\saml\NameID($nameId);
334
            }
335
        }
336
337
        $subjectConfirmation = Utils::xpQuery($subject, './saml_assertion:SubjectConfirmation');
338
        if (empty($subjectConfirmation) && empty($nameId)) {
339
            throw new \Exception('Missing <saml:SubjectConfirmation> in <saml:Subject>.');
340
        }
341
342
        foreach ($subjectConfirmation as $sc) {
343
            $this->SubjectConfirmation[] = new SubjectConfirmation($sc);
344
        }
345
    }
346
347
    /**
348
     * Parse conditions in assertion.
349
     *
350
     * @param \DOMElement $xml The assertion XML element.
351
     * @throws \Exception
352
     */
353
    private function parseConditions(\DOMElement $xml)
354
    {
355
        $conditions = Utils::xpQuery($xml, './saml_assertion:Conditions');
356
        if (empty($conditions)) {
357
            /* No <saml:Conditions> node. */
358
359
            return;
360
        } elseif (count($conditions) > 1) {
361
            throw new \Exception('More than one <saml:Conditions> in <saml:Assertion>.');
362
        }
363
        $conditions = $conditions[0];
364
365 View Code Duplication
        if ($conditions->hasAttribute('NotBefore')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
366
            $notBefore = Utils::xsDateTimeToTimestamp($conditions->getAttribute('NotBefore'));
367
            if ($this->notBefore === null || $this->notBefore < $notBefore) {
368
                $this->notBefore = $notBefore;
369
            }
370
        }
371 View Code Duplication
        if ($conditions->hasAttribute('NotOnOrAfter')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
372
            $notOnOrAfter = Utils::xsDateTimeToTimestamp($conditions->getAttribute('NotOnOrAfter'));
373
            if ($this->notOnOrAfter === null || $this->notOnOrAfter > $notOnOrAfter) {
374
                $this->notOnOrAfter = $notOnOrAfter;
375
            }
376
        }
377
378
        for ($node = $conditions->firstChild; $node !== null; $node = $node->nextSibling) {
379
            if ($node instanceof \DOMText) {
380
                continue;
381
            }
382 View Code Duplication
            if ($node->namespaceURI !== Constants::NS_SAML) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
383
                throw new \Exception('Unknown namespace of condition: ' . var_export($node->namespaceURI, true));
384
            }
385
            switch ($node->localName) {
386
                case 'AudienceRestriction':
387
                    $audiences = Utils::extractStrings($node, Constants::NS_SAML, 'Audience');
0 ignored issues
show
Compatibility introduced by
$node of type object<DOMNode> is not a sub-type of object<DOMElement>. It seems like you assume a child class of the class DOMNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
388
                    if ($this->validAudiences === null) {
389
                        /* The first (and probably last) AudienceRestriction element. */
390
                        $this->validAudiences = $audiences;
391
                    } else {
392
                        /*
393
                         * The set of AudienceRestriction are ANDed together, so we need
394
                         * the subset that are present in all of them.
395
                         */
396
                        $this->validAudiences = array_intersect($this->validAudiences, $audiences);
397
                    }
398
                    break;
399
                case 'OneTimeUse':
400
                    /* Currently ignored. */
401
                    break;
402
                case 'ProxyRestriction':
403
                    /* Currently ignored. */
404
                    break;
405
                default:
406
                    throw new \Exception('Unknown condition: ' . var_export($node->localName, true));
407
            }
408
        }
409
    }
410
411
    /**
412
     * Parse AuthnStatement in assertion.
413
     *
414
     * @param \DOMElement $xml The assertion XML element.
415
     * @throws \Exception
416
     */
417
    private function parseAuthnStatement(\DOMElement $xml)
418
    {
419
        $authnStatements = Utils::xpQuery($xml, './saml_assertion:AuthnStatement');
420
        if (empty($authnStatements)) {
421
            $this->authnInstant = null;
422
423
            return;
424
        } elseif (count($authnStatements) > 1) {
425
            throw new \Exception('More than one <saml:AuthnStatement> in <saml:Assertion> not supported.');
426
        }
427
        $authnStatement = $authnStatements[0];
428
429
        if (!$authnStatement->hasAttribute('AuthnInstant')) {
430
            throw new \Exception('Missing required AuthnInstant attribute on <saml:AuthnStatement>.');
431
        }
432
        $this->authnInstant = Utils::xsDateTimeToTimestamp($authnStatement->getAttribute('AuthnInstant'));
433
434
        if ($authnStatement->hasAttribute('SessionNotOnOrAfter')) {
435
            $this->sessionNotOnOrAfter = Utils::xsDateTimeToTimestamp($authnStatement->getAttribute('SessionNotOnOrAfter'));
436
        }
437
438
        if ($authnStatement->hasAttribute('SessionIndex')) {
439
            $this->sessionIndex = $authnStatement->getAttribute('SessionIndex');
440
        }
441
442
        $this->parseAuthnContext($authnStatement);
443
    }
444
445
    /**
446
     * Parse AuthnContext in AuthnStatement.
447
     *
448
     * @param \DOMElement $authnStatementEl
449
     * @throws \Exception
450
     */
451
    private function parseAuthnContext(\DOMElement $authnStatementEl)
452
    {
453
        // Get the AuthnContext element
454
        $authnContexts = Utils::xpQuery($authnStatementEl, './saml_assertion:AuthnContext');
455
        if (count($authnContexts) > 1) {
456
            throw new \Exception('More than one <saml:AuthnContext> in <saml:AuthnStatement>.');
457
        } elseif (empty($authnContexts)) {
458
            throw new \Exception('Missing required <saml:AuthnContext> in <saml:AuthnStatement>.');
459
        }
460
        $authnContextEl = $authnContexts[0];
461
462
        // Get the AuthnContextDeclRef (if available)
463
        $authnContextDeclRefs = Utils::xpQuery($authnContextEl, './saml_assertion:AuthnContextDeclRef');
464
        if (count($authnContextDeclRefs) > 1) {
465
            throw new \Exception(
466
                'More than one <saml:AuthnContextDeclRef> found?'
467
            );
468
        } elseif (count($authnContextDeclRefs) === 1) {
469
            $this->setAuthnContextDeclRef(trim($authnContextDeclRefs[0]->textContent));
470
        }
471
472
        // Get the AuthnContextDecl (if available)
473
        $authnContextDecls = Utils::xpQuery($authnContextEl, './saml_assertion:AuthnContextDecl');
474
        if (count($authnContextDecls) > 1) {
475
            throw new \Exception(
476
                'More than one <saml:AuthnContextDecl> found?'
477
            );
478
        } elseif (count($authnContextDecls) === 1) {
479
            $this->setAuthnContextDecl(new Chunk($authnContextDecls[0]));
480
        }
481
482
        // Get the AuthnContextClassRef (if available)
483
        $authnContextClassRefs = Utils::xpQuery($authnContextEl, './saml_assertion:AuthnContextClassRef');
484
        if (count($authnContextClassRefs) > 1) {
485
            throw new \Exception('More than one <saml:AuthnContextClassRef> in <saml:AuthnContext>.');
486
        } elseif (count($authnContextClassRefs) === 1) {
487
            $this->setAuthnContextClassRef(trim($authnContextClassRefs[0]->textContent));
488
        }
489
490
        // Constraint from XSD: MUST have one of the three
491
        if (empty($this->authnContextClassRef) && empty($this->authnContextDecl) && empty($this->authnContextDeclRef)) {
492
            throw new \Exception(
493
                'Missing either <saml:AuthnContextClassRef> or <saml:AuthnContextDeclRef> or <saml:AuthnContextDecl>'
494
            );
495
        }
496
497
        $this->AuthenticatingAuthority = Utils::extractStrings(
498
            $authnContextEl,
499
            Constants::NS_SAML,
500
            'AuthenticatingAuthority'
501
        );
502
    }
503
504
    /**
505
     * Parse attribute statements in assertion.
506
     *
507
     * @param \DOMElement $xml The XML element with the assertion.
508
     * @throws \Exception
509
     */
510
    private function parseAttributes(\DOMElement $xml)
511
    {
512
        $firstAttribute = true;
513
        $attributes = Utils::xpQuery($xml, './saml_assertion:AttributeStatement/saml_assertion:Attribute');
514
        foreach ($attributes as $attribute) {
515
            if (!$attribute->hasAttribute('Name')) {
516
                throw new \Exception('Missing name on <saml:Attribute> element.');
517
            }
518
            $name = $attribute->getAttribute('Name');
519
520
            if ($attribute->hasAttribute('NameFormat')) {
521
                $nameFormat = $attribute->getAttribute('NameFormat');
522
            } else {
523
                $nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
524
            }
525
526 View Code Duplication
            if ($firstAttribute) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
527
                $this->nameFormat = $nameFormat;
528
                $firstAttribute = false;
529
            } else {
530
                if ($this->nameFormat !== $nameFormat) {
531
                    $this->nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
532
                }
533
            }
534
535
            if (!array_key_exists($name, $this->attributes)) {
536
                $this->attributes[$name] = array();
537
                $this->attributesValueTypes[$name] = array();
538
            }
539
540
            $this->parseAttributeValue($attribute, $name);
541
        }
542
    }
543
544
    /**
545
     * @param \DOMNode $attribute
546
     * @param string   $attributeName
547
     */
548
    private function parseAttributeValue($attribute, $attributeName)
549
    {
550
        /** @var \DOMElement[] $values */
551
        $values = Utils::xpQuery($attribute, './saml_assertion:AttributeValue');
552
553
        if ($attributeName === Constants::EPTI_URN_MACE || $attributeName === Constants::EPTI_URN_OID) {
554
            foreach ($values as $index => $eptiAttributeValue) {
555
                $eptiNameId = Utils::xpQuery($eptiAttributeValue, './saml_assertion:NameID');
556
557
                if (count($eptiNameId) === 1) {
558
                    $this->attributes[$attributeName][] = new XML\saml\NameID($eptiNameId[0]);
559
                } else {
560
                    /* Fall back for legacy IdPs sending string value (e.g. SSP < 1.15) */
561
                    Utils::getContainer()->getLogger()->warning(sprintf("Attribute %s (EPTI) value %d is not an XML NameId", $attributeName, $index));
562
                    $this->attributes[$attributeName][] = XML\saml\NameID::fromArray(['Value' => $eptiAttributeValue->textContent]);
0 ignored issues
show
Deprecated Code introduced by
The method SAML2\XML\saml\NameIDType::fromArray() has been deprecated.

This method has been deprecated.

Loading history...
563
                }
564
            }
565
566
            return;
567
        }
568
569
        foreach ($values as $value) {
570
            $hasNonTextChildElements = false;
571
            foreach ($value->childNodes as $childNode) {
572
                /** @var \DOMNode $childNode */
573
                if ($childNode->nodeType !== XML_TEXT_NODE) {
574
                    $hasNonTextChildElements = true;
575
                    break;
576
                }
577
            }
578
579
            $type = $value->getAttribute('xsi:type');
580
            if ($type === '') {
581
                $type = null;
582
            }
583
            $this->attributesValueTypes[$attributeName][] = $type;
584
585
            if ($hasNonTextChildElements) {
586
                $this->attributes[$attributeName][] = $value->childNodes;
587
                continue;
588
            }
589
            
590
            if ($type === 'xs:integer') {
591
                $this->attributes[$attributeName][] = (int)$value->textContent;
592
            } else {
593
                $this->attributes[$attributeName][] = trim($value->textContent);
594
            }
595
        }
596
    }
597
598
    /**
599
     * Parse encrypted attribute statements in assertion.
600
     *
601
     * @param \DOMElement $xml The XML element with the assertion.
602
     */
603
    private function parseEncryptedAttributes(\DOMElement $xml)
604
    {
605
        $this->encryptedAttributes = Utils::xpQuery(
606
            $xml,
607
            './saml_assertion:AttributeStatement/saml_assertion:EncryptedAttribute'
608
        );
609
    }
610
611
    /**
612
     * Parse signature on assertion.
613
     *
614
     * @param \DOMElement $xml The assertion XML element.
615
     */
616
    private function parseSignature(\DOMElement $xml)
617
    {
618
        /** @var null|\DOMAttr $signatureMethod */
619
        $signatureMethod = Utils::xpQuery($xml, './ds:Signature/ds:SignedInfo/ds:SignatureMethod/@Algorithm');
620
621
        /* Validate the signature element of the message. */
622
        $sig = Utils::validateElement($xml);
623
        if ($sig !== false) {
624
            $this->wasSignedAtConstruction = true;
625
            $this->certificates = $sig['Certificates'];
626
            $this->signatureData = $sig;
627
            $this->signatureMethod = $signatureMethod[0]->value;
628
        }
629
    }
630
631
    /**
632
     * Validate this assertion against a public key.
633
     *
634
     * If no signature was present on the assertion, we will return false.
635
     * Otherwise, true will be returned. An exception is thrown if the
636
     * signature validation fails.
637
     *
638
     * @param  XMLSecurityKey $key The key we should check against.
639
     * @return boolean        true if successful, false if it is unsigned.
640
     */
641
    public function validate(XMLSecurityKey $key)
642
    {
643
        assert($key->type === \RobRichards\XMLSecLibs\XMLSecurityKey::RSA_SHA256);
644
645
        if ($this->signatureData === null) {
646
            return false;
647
        }
648
649
        Utils::validateSignature($this->signatureData, $key);
650
651
        return true;
652
    }
653
654
    /**
655
     * Retrieve the identifier of this assertion.
656
     *
657
     * @return string The identifier of this assertion.
658
     */
659
    public function getId()
660
    {
661
        return $this->id;
662
    }
663
664
    /**
665
     * Set the identifier of this assertion.
666
     *
667
     * @param string $id The new identifier of this assertion.
668
     */
669
    public function setId($id)
670
    {
671
        assert(is_string($id));
672
673
        $this->id = $id;
674
    }
675
676
    /**
677
     * Retrieve the issue timestamp of this assertion.
678
     *
679
     * @return int The issue timestamp of this assertion, as an UNIX timestamp.
680
     */
681
    public function getIssueInstant()
682
    {
683
        return $this->issueInstant;
684
    }
685
686
    /**
687
     * Set the issue timestamp of this assertion.
688
     *
689
     * @param int $issueInstant The new issue timestamp of this assertion, as an UNIX timestamp.
690
     */
691
    public function setIssueInstant($issueInstant)
692
    {
693
        assert(is_int($issueInstant));
694
695
        $this->issueInstant = $issueInstant;
696
    }
697
698
    /**
699
     * Retrieve the issuer if this assertion.
700
     *
701
     * @return string|\SAML2\XML\saml\Issuer The issuer of this assertion.
702
     */
703
    public function getIssuer()
704
    {
705
        return $this->issuer;
706
    }
707
708
    /**
709
     * Set the issuer of this message.
710
     *
711
     * @param string|\SAML2\XML\saml\Issuer $issuer The new issuer of this assertion.
712
     */
713
    public function setIssuer($issuer)
714
    {
715
        assert(is_string($issuer) || $issuer instanceof XML\saml\Issuer);
716
717
        $this->issuer = $issuer;
718
    }
719
720
    /**
721
     * Retrieve the NameId of the subject in the assertion.
722
     *
723
     * @return \SAML2\XML\saml\NameID|null The name identifier of the assertion.
724
     * @throws \Exception
725
     */
726
    public function getNameId()
727
    {
728
        if ($this->encryptedNameId !== null) {
729
            throw new \Exception('Attempted to retrieve encrypted NameID without decrypting it first.');
730
        }
731
732
        return $this->nameId;
733
    }
734
735
    /**
736
     * Set the NameId of the subject in the assertion.
737
     *
738
     * The NameId must be a \SAML2\XML\saml\NameID object or an array in the format accepted by
739
     * \SAML2\Utils::addNameId() (an array, deprecated).
740
     *
741
     * @see \SAML2\Utils::addNameId()
742
     * @param \SAML2\XML\saml\NameID|array|null $nameId The name identifier of the assertion.
743
     */
744 View Code Duplication
    public function setNameId($nameId)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
745
    {
746
        assert(is_array($nameId) || is_null($nameId) || $nameId instanceof XML\saml\NameID);
747
748
        if (is_array($nameId)) {
749
            $nameId = XML\saml\NameID::fromArray($nameId);
0 ignored issues
show
Deprecated Code introduced by
The method SAML2\XML\saml\NameIDType::fromArray() has been deprecated.

This method has been deprecated.

Loading history...
750
        }
751
        $this->nameId = $nameId;
752
    }
753
754
    /**
755
     * Check whether the NameId is encrypted.
756
     *
757
     * @return true if the NameId is encrypted, false if not.
758
     */
759
    public function isNameIdEncrypted()
760
    {
761
        return $this->encryptedNameId !== null;
762
    }
763
764
    /**
765
     * Encrypt the NameID in the Assertion.
766
     *
767
     * @param XMLSecurityKey $key The encryption key.
768
     */
769 View Code Duplication
    public function encryptNameId(XMLSecurityKey $key)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
770
    {
771
        /* First create a XML representation of the NameID. */
772
        $doc = DOMDocumentFactory::create();
773
        $root = $doc->createElement('root');
774
        $doc->appendChild($root);
775
        $this->nameId->toXML($root);
776
        $nameId = $root->firstChild;
777
778
        Utils::getContainer()->debugMessage($nameId, 'encrypt');
0 ignored issues
show
Documentation introduced by
$nameId is of type object<DOMNode>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
779
780
        /* Encrypt the NameID. */
781
        $enc = new XMLSecEnc();
782
        $enc->setNode($nameId);
783
        // @codingStandardsIgnoreStart
784
        $enc->type = XMLSecEnc::Element;
785
        // @codingStandardsIgnoreEnd
786
787
        $symmetricKey = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
788
        $symmetricKey->generateSessionKey();
789
        $enc->encryptKey($key, $symmetricKey);
790
791
        $this->encryptedNameId = $enc->encryptNode($symmetricKey);
0 ignored issues
show
Documentation Bug introduced by
It seems like $enc->encryptNode($symmetricKey) can also be of type object<DOMNode>. However, the property $encryptedNameId is declared as type object<DOMElement>|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
792
        $this->nameId = null;
793
    }
794
795
    /**
796
     * Decrypt the NameId of the subject in the assertion.
797
     *
798
     * @param XMLSecurityKey $key       The decryption key.
799
     * @param array          $blacklist Blacklisted decryption algorithms.
800
     */
801 View Code Duplication
    public function decryptNameId(XMLSecurityKey $key, array $blacklist = array())
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
802
    {
803
        if ($this->encryptedNameId === null) {
804
            /* No NameID to decrypt. */
805
806
            return;
807
        }
808
809
        $nameId = Utils::decryptElement($this->encryptedNameId, $key, $blacklist);
810
        Utils::getContainer()->debugMessage($nameId, 'decrypt');
0 ignored issues
show
Documentation introduced by
$nameId is of type object<DOMElement>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
811
        $this->nameId = new XML\saml\NameID($nameId);
812
813
        $this->encryptedNameId = null;
814
    }
815
816
    /**
817
     * Did this Assertion contain encrypted Attributes?
818
     *
819
     * @return bool
820
     */
821
    public function hasEncryptedAttributes()
822
    {
823
        return $this->encryptedAttributes !== [];
824
    }
825
826
    /**
827
     * Decrypt the assertion attributes.
828
     *
829
     * @param XMLSecurityKey $key
830
     * @param array $blacklist
831
     * @throws \Exception
832
     */
833
    public function decryptAttributes(XMLSecurityKey $key, array $blacklist = array())
834
    {
835
        if (!$this->hasEncryptedAttributes()) {
836
            return;
837
        }
838
        $firstAttribute = true;
839
        $attributes = $this->encryptedAttributes;
840
        foreach ($attributes as $attributeEnc) {
841
            /*Decrypt node <EncryptedAttribute>*/
842
            $attribute = Utils::decryptElement(
843
                $attributeEnc->getElementsByTagName('EncryptedData')->item(0),
0 ignored issues
show
Compatibility introduced by
$attributeEnc->getElemen...ncryptedData')->item(0) of type object<DOMNode> is not a sub-type of object<DOMElement>. It seems like you assume a child class of the class DOMNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
844
                $key,
845
                $blacklist
846
            );
847
848
            if (!$attribute->hasAttribute('Name')) {
849
                throw new \Exception('Missing name on <saml:Attribute> element.');
850
            }
851
            $name = $attribute->getAttribute('Name');
852
853
            if ($attribute->hasAttribute('NameFormat')) {
854
                $nameFormat = $attribute->getAttribute('NameFormat');
855
            } else {
856
                $nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
857
            }
858
859 View Code Duplication
            if ($firstAttribute) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
860
                $this->nameFormat = $nameFormat;
861
                $firstAttribute = false;
862
            } else {
863
                if ($this->nameFormat !== $nameFormat) {
864
                    $this->nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
865
                }
866
            }
867
868
            if (!array_key_exists($name, $this->attributes)) {
869
                $this->attributes[$name] = array();
870
            }
871
872
            $this->parseAttributeValue($attribute, $name);
873
        }
874
    }
875
876
    /**
877
     * Retrieve the earliest timestamp this assertion is valid.
878
     *
879
     * This function returns null if there are no restrictions on how early the
880
     * assertion can be used.
881
     *
882
     * @return int|null The earliest timestamp this assertion is valid.
883
     */
884
    public function getNotBefore()
885
    {
886
        return $this->notBefore;
887
    }
888
889
    /**
890
     * Set the earliest timestamp this assertion can be used.
891
     *
892
     * Set this to null if no limit is required.
893
     *
894
     * @param int|null $notBefore The earliest timestamp this assertion is valid.
895
     */
896
    public function setNotBefore($notBefore)
897
    {
898
        assert(is_int($notBefore) || is_null($notBefore));
899
900
        $this->notBefore = $notBefore;
901
    }
902
903
    /**
904
     * Retrieve the expiration timestamp of this assertion.
905
     *
906
     * This function returns null if there are no restrictions on how
907
     * late the assertion can be used.
908
     *
909
     * @return int|null The latest timestamp this assertion is valid.
910
     */
911
    public function getNotOnOrAfter()
912
    {
913
        return $this->notOnOrAfter;
914
    }
915
916
    /**
917
     * Set the expiration timestamp of this assertion.
918
     *
919
     * Set this to null if no limit is required.
920
     *
921
     * @param int|null $notOnOrAfter The latest timestamp this assertion is valid.
922
     */
923
    public function setNotOnOrAfter($notOnOrAfter)
924
    {
925
        assert(is_int($notOnOrAfter) || is_null($notOnOrAfter));
926
927
        $this->notOnOrAfter = $notOnOrAfter;
928
    }
929
930
    /**
931
     * Set $EncryptedAttributes if attributes will send encrypted
932
     *
933
     * @param boolean $ea true to encrypt attributes in the assertion.
934
     */
935
    public function setEncryptedAttributes($ea)
936
    {
937
        $this->requiredEncAttributes = $ea;
938
    }
939
940
    /**
941
     * Retrieve the audiences that are allowed to receive this assertion.
942
     *
943
     * This may be null, in which case all audiences are allowed.
944
     *
945
     * @return array|null The allowed audiences.
946
     */
947
    public function getValidAudiences()
948
    {
949
        return $this->validAudiences;
950
    }
951
952
    /**
953
     * Set the audiences that are allowed to receive this assertion.
954
     *
955
     * This may be null, in which case all audiences are allowed.
956
     *
957
     * @param array|null $validAudiences The allowed audiences.
958
     */
959
    public function setValidAudiences(array $validAudiences = null)
960
    {
961
        $this->validAudiences = $validAudiences;
962
    }
963
964
    /**
965
     * Retrieve the AuthnInstant of the assertion.
966
     *
967
     * @return int|null The timestamp the user was authenticated, or NULL if the user isn't authenticated.
968
     */
969
    public function getAuthnInstant()
970
    {
971
        return $this->authnInstant;
972
    }
973
974
975
    /**
976
     * Set the AuthnInstant of the assertion.
977
     *
978
     * @param int|null $authnInstant Timestamp the user was authenticated, or NULL if we don't want an AuthnStatement.
979
     */
980
    public function setAuthnInstant($authnInstant)
981
    {
982
        assert(is_int($authnInstant) || is_null($authnInstant));
983
984
        $this->authnInstant = $authnInstant;
985
    }
986
987
    /**
988
     * Retrieve the session expiration timestamp.
989
     *
990
     * This function returns null if there are no restrictions on the
991
     * session lifetime.
992
     *
993
     * @return int|null The latest timestamp this session is valid.
994
     */
995
    public function getSessionNotOnOrAfter()
996
    {
997
        return $this->sessionNotOnOrAfter;
998
    }
999
1000
    /**
1001
     * Set the session expiration timestamp.
1002
     *
1003
     * Set this to null if no limit is required.
1004
     *
1005
     * @param int|null $sessionNotOnOrAfter The latest timestamp this session is valid.
1006
     */
1007
    public function setSessionNotOnOrAfter($sessionNotOnOrAfter)
1008
    {
1009
        assert(is_int($sessionNotOnOrAfter) || is_null($sessionNotOnOrAfter));
1010
1011
        $this->sessionNotOnOrAfter = $sessionNotOnOrAfter;
1012
    }
1013
1014
    /**
1015
     * Retrieve the session index of the user at the IdP.
1016
     *
1017
     * @return string|null The session index of the user at the IdP.
1018
     */
1019
    public function getSessionIndex()
1020
    {
1021
        return $this->sessionIndex;
1022
    }
1023
1024
    /**
1025
     * Set the session index of the user at the IdP.
1026
     *
1027
     * Note that the authentication context must be set before the
1028
     * session index can be inluded in the assertion.
1029
     *
1030
     * @param string|null $sessionIndex The session index of the user at the IdP.
1031
     */
1032
    public function setSessionIndex($sessionIndex)
1033
    {
1034
        assert(is_string($sessionIndex) || is_null($sessionIndex));
1035
1036
        $this->sessionIndex = $sessionIndex;
1037
    }
1038
1039
    /**
1040
     * Retrieve the authentication method used to authenticate the user.
1041
     *
1042
     * This will return null if no authentication statement was
1043
     * included in the assertion.
1044
     *
1045
     * Note that this returns either the AuthnContextClassRef or the AuthnConextDeclRef, whose definition overlaps
1046
     * but is slightly different (consult the specification for more information).
1047
     * This was done to work around an old bug of Shibboleth ( https://bugs.internet2.edu/jira/browse/SIDP-187 ).
1048
     * Should no longer be required, please use either getAuthnConextClassRef or getAuthnContextDeclRef.
1049
     *
1050
     * @deprecated use getAuthnContextClassRef
1051
     * @return string|null The authentication method.
1052
     */
1053
    public function getAuthnContext()
1054
    {
1055
        if (!empty($this->authnContextClassRef)) {
1056
            return $this->authnContextClassRef;
1057
        }
1058
        if (!empty($this->authnContextDeclRef)) {
1059
            return $this->authnContextDeclRef;
1060
        }
1061
        return null;
1062
    }
1063
1064
    /**
1065
     * Set the authentication method used to authenticate the user.
1066
     *
1067
     * If this is set to null, no authentication statement will be
1068
     * included in the assertion. The default is null.
1069
     *
1070
     * @deprecated use setAuthnContextClassRef
1071
     * @param string|null $authnContext The authentication method.
1072
     */
1073
    public function setAuthnContext($authnContext)
1074
    {
1075
        $this->setAuthnContextClassRef($authnContext);
1076
    }
1077
1078
    /**
1079
     * Retrieve the authentication method used to authenticate the user.
1080
     *
1081
     * This will return null if no authentication statement was
1082
     * included in the assertion.
1083
     *
1084
     * @return string|null The authentication method.
1085
     */
1086
    public function getAuthnContextClassRef()
1087
    {
1088
        return $this->authnContextClassRef;
1089
    }
1090
1091
    /**
1092
     * Set the authentication method used to authenticate the user.
1093
     *
1094
     * If this is set to null, no authentication statement will be
1095
     * included in the assertion. The default is null.
1096
     *
1097
     * @param string|null $authnContextClassRef The authentication method.
1098
     */
1099
    public function setAuthnContextClassRef($authnContextClassRef)
1100
    {
1101
        assert(is_string($authnContextClassRef) || is_null($authnContextClassRef));
1102
1103
        $this->authnContextClassRef = $authnContextClassRef;
1104
    }
1105
1106
    /**
1107
     * Set the authentication context declaration.
1108
     *
1109
     * @param \SAML2\XML\Chunk $authnContextDecl
1110
     * @throws \Exception
1111
     */
1112
    public function setAuthnContextDecl(Chunk $authnContextDecl)
1113
    {
1114
        if (!empty($this->authnContextDeclRef)) {
1115
            throw new \Exception(
1116
                'AuthnContextDeclRef is already registered! May only have either a Decl or a DeclRef, not both!'
1117
            );
1118
        }
1119
1120
        $this->authnContextDecl = $authnContextDecl;
1121
    }
1122
1123
    /**
1124
     * Get the authentication context declaration.
1125
     *
1126
     * See:
1127
     * @url http://docs.oasis-open.org/security/saml/v2.0/saml-authn-context-2.0-os.pdf
1128
     *
1129
     * @return \SAML2\XML\Chunk|null
1130
     */
1131
    public function getAuthnContextDecl()
1132
    {
1133
        return $this->authnContextDecl;
1134
    }
1135
1136
    /**
1137
     * Set the authentication context declaration reference.
1138
     *
1139
     * @param string $authnContextDeclRef
1140
     * @throws \Exception
1141
     */
1142
    public function setAuthnContextDeclRef($authnContextDeclRef)
1143
    {
1144
        if (!empty($this->authnContextDecl)) {
1145
            throw new \Exception(
1146
                'AuthnContextDecl is already registered! May only have either a Decl or a DeclRef, not both!'
1147
            );
1148
        }
1149
1150
        $this->authnContextDeclRef = $authnContextDeclRef;
0 ignored issues
show
Documentation Bug introduced by
It seems like $authnContextDeclRef of type string is incompatible with the declared type object<SAML2\XML\Chunk> of property $authnContextDeclRef.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1151
    }
1152
1153
    /**
1154
     * Get the authentication context declaration reference.
1155
     * URI reference that identifies an authentication context declaration.
1156
     *
1157
     * The URI reference MAY directly resolve into an XML document containing the referenced declaration.
1158
     *
1159
     * @return string
1160
     */
1161
    public function getAuthnContextDeclRef()
1162
    {
1163
        return $this->authnContextDeclRef;
1164
    }
1165
1166
    /**
1167
     * Retrieve the AuthenticatingAuthority.
1168
     *
1169
     *
1170
     * @return array
1171
     */
1172
    public function getAuthenticatingAuthority()
1173
    {
1174
        return $this->AuthenticatingAuthority;
1175
    }
1176
1177
    /**
1178
     * Set the AuthenticatingAuthority
1179
     *
1180
     *
1181
     * @param array.
1182
     */
1183
    public function setAuthenticatingAuthority($authenticatingAuthority)
1184
    {
1185
        $this->AuthenticatingAuthority = $authenticatingAuthority;
1186
    }
1187
1188
    /**
1189
     * Retrieve all attributes.
1190
     *
1191
     * @return array All attributes, as an associative array.
1192
     */
1193
    public function getAttributes()
1194
    {
1195
        return $this->attributes;
1196
    }
1197
1198
    /**
1199
     * Replace all attributes.
1200
     *
1201
     * @param array $attributes All new attributes, as an associative array.
1202
     */
1203
    public function setAttributes(array $attributes)
1204
    {
1205
        $this->attributes = $attributes;
1206
    }
1207
1208
    /**
1209
     * Retrieve all attributes value types.
1210
     *
1211
     * @return array All attributes value types, as an associative array.
1212
     */
1213
    public function getAttributesValueTypes()
1214
    {
1215
        return $this->attributesValueTypes;
1216
    }
1217
1218
    /**
1219
     * Replace all attributes value types..
1220
     *
1221
     * @param array $attributesValueTypes All new attribute value types, as an associative array.
1222
     */
1223
    public function setAttributesValueTypes(array $attributesValueTypes)
1224
    {
1225
        $this->attributesValueTypes = $attributesValueTypes;
1226
    }
1227
1228
    /**
1229
     * Retrieve the NameFormat used on all attributes.
1230
     *
1231
     * If more than one NameFormat is used in the received attributes, this
1232
     * returns the unspecified NameFormat.
1233
     *
1234
     * @return string The NameFormat used on all attributes.
1235
     */
1236
    public function getAttributeNameFormat()
1237
    {
1238
        return $this->nameFormat;
1239
    }
1240
1241
    /**
1242
     * Set the NameFormat used on all attributes.
1243
     *
1244
     * @param string $nameFormat The NameFormat used on all attributes.
1245
     */
1246
    public function setAttributeNameFormat($nameFormat)
1247
    {
1248
        assert(is_string($nameFormat));
1249
1250
        $this->nameFormat = $nameFormat;
1251
    }
1252
1253
    /**
1254
     * Retrieve the SubjectConfirmation elements we have in our Subject element.
1255
     *
1256
     * @return array Array of \SAML2\XML\saml\SubjectConfirmation elements.
1257
     */
1258
    public function getSubjectConfirmation()
1259
    {
1260
        return $this->SubjectConfirmation;
1261
    }
1262
1263
    /**
1264
     * Set the SubjectConfirmation elements that should be included in the assertion.
1265
     *
1266
     * @param array $SubjectConfirmation Array of \SAML2\XML\saml\SubjectConfirmation elements.
1267
     */
1268
    public function setSubjectConfirmation(array $SubjectConfirmation)
1269
    {
1270
        $this->SubjectConfirmation = $SubjectConfirmation;
1271
    }
1272
1273
    /**
1274
     * Retrieve the private key we should use to sign the assertion.
1275
     *
1276
     * @return XMLSecurityKey|null The key, or NULL if no key is specified.
1277
     */
1278
    public function getSignatureKey()
1279
    {
1280
        return $this->signatureKey;
1281
    }
1282
1283
    /**
1284
     * Set the private key we should use to sign the assertion.
1285
     *
1286
     * If the key is null, the assertion will be sent unsigned.
1287
     *
1288
     * @param XMLSecurityKey|null $signatureKey
1289
     */
1290
    public function setSignatureKey(XMLSecurityKey $signatureKey = null)
1291
    {
1292
        $this->signatureKey = $signatureKey;
1293
    }
1294
1295
    /**
1296
     * Return the key we should use to encrypt the assertion.
1297
     *
1298
     * @return XMLSecurityKey|null The key, or NULL if no key is specified..
1299
     *
1300
     */
1301
    public function getEncryptionKey()
1302
    {
1303
        return $this->encryptionKey;
1304
    }
1305
1306
    /**
1307
     * Set the private key we should use to encrypt the attributes.
1308
     *
1309
     * @param XMLSecurityKey|null $Key
1310
     */
1311
    public function setEncryptionKey(XMLSecurityKey $Key = null)
1312
    {
1313
        $this->encryptionKey = $Key;
1314
    }
1315
1316
    /**
1317
     * Set the certificates that should be included in the assertion.
1318
     *
1319
     * The certificates should be strings with the PEM encoded data.
1320
     *
1321
     * @param array $certificates An array of certificates.
1322
     */
1323
    public function setCertificates(array $certificates)
1324
    {
1325
        $this->certificates = $certificates;
1326
    }
1327
1328
    /**
1329
     * Retrieve the certificates that are included in the assertion.
1330
     *
1331
     * @return array An array of certificates.
1332
     */
1333
    public function getCertificates()
1334
    {
1335
        return $this->certificates;
1336
    }
1337
1338
    /**
1339
     * @return bool
1340
     */
1341
    public function getWasSignedAtConstruction()
1342
    {
1343
        return $this->wasSignedAtConstruction;
1344
    }
1345
1346
    /**
1347
     * @return null|string
1348
     */
1349
    public function getSignatureMethod()
1350
    {
1351
        return $this->signatureMethod;
1352
    }
1353
1354
    /**
1355
     * Convert this assertion to an XML element.
1356
     *
1357
     * @param  \DOMNode|null $parentElement The DOM node the assertion should be created in.
1358
     * @return \DOMElement   This assertion.
1359
     */
1360
    public function toXML(\DOMNode $parentElement = null)
1361
    {
1362 View Code Duplication
        if ($parentElement === null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1363
            $document = DOMDocumentFactory::create();
1364
            $parentElement = $document;
1365
        } else {
1366
            $document = $parentElement->ownerDocument;
1367
        }
1368
1369
        $root = $document->createElementNS(Constants::NS_SAML, 'saml:' . 'Assertion');
1370
        $parentElement->appendChild($root);
1371
1372
        /* Ugly hack to add another namespace declaration to the root element. */
1373
        $root->setAttributeNS(Constants::NS_SAMLP, 'samlp:tmp', 'tmp');
1374
        $root->removeAttributeNS(Constants::NS_SAMLP, 'tmp');
1375
        $root->setAttributeNS(Constants::NS_XSI, 'xsi:tmp', 'tmp');
1376
        $root->removeAttributeNS(Constants::NS_XSI, 'tmp');
1377
        $root->setAttributeNS(Constants::NS_XS, 'xs:tmp', 'tmp');
1378
        $root->removeAttributeNS(Constants::NS_XS, 'tmp');
1379
1380
        $root->setAttribute('ID', $this->id);
1381
        $root->setAttribute('Version', '2.0');
1382
        $root->setAttribute('IssueInstant', gmdate('Y-m-d\TH:i:s\Z', $this->issueInstant));
1383
1384 View Code Duplication
        if (is_string($this->issuer)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1385
            $issuer = Utils::addString($root, Constants::NS_SAML, 'saml:Issuer', $this->issuer);
1386
        } elseif ($this->issuer instanceof XML\saml\Issuer) {
1387
            $issuer = $this->issuer->toXML($root);
1388
        }
1389
1390
        $this->addSubject($root);
1391
        $this->addConditions($root);
1392
        $this->addAuthnStatement($root);
1393
        if ($this->requiredEncAttributes === false) {
1394
            $this->addAttributeStatement($root);
1395
        } else {
1396
            $this->addEncryptedAttributeStatement($root);
1397
        }
1398
1399
        if ($this->signatureKey !== null) {
1400
            Utils::insertSignature($this->signatureKey, $this->certificates, $root, $issuer->nextSibling);
0 ignored issues
show
Bug introduced by
The variable $issuer does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1401
        }
1402
1403
        return $root;
1404
    }
1405
1406
    /**
1407
     * Add a Subject-node to the assertion.
1408
     *
1409
     * @param \DOMElement $root The assertion element we should add the subject to.
1410
     */
1411 View Code Duplication
    private function addSubject(\DOMElement $root)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1412
    {
1413
        if ($this->nameId === null && $this->encryptedNameId === null) {
1414
            /* We don't have anything to create a Subject node for. */
1415
1416
            return;
1417
        }
1418
1419
        $subject = $root->ownerDocument->createElementNS(Constants::NS_SAML, 'saml:Subject');
1420
        $root->appendChild($subject);
1421
1422
        if ($this->encryptedNameId === null) {
1423
            $this->nameId->toXML($subject);
1424
        } else {
1425
            $eid = $subject->ownerDocument->createElementNS(Constants::NS_SAML, 'saml:' . 'EncryptedID');
1426
            $subject->appendChild($eid);
1427
            $eid->appendChild($subject->ownerDocument->importNode($this->encryptedNameId, true));
1428
        }
1429
1430
        foreach ($this->SubjectConfirmation as $sc) {
1431
            $sc->toXML($subject);
1432
        }
1433
    }
1434
1435
1436
    /**
1437
     * Add a Conditions-node to the assertion.
1438
     *
1439
     * @param \DOMElement $root The assertion element we should add the conditions to.
1440
     */
1441
    private function addConditions(\DOMElement $root)
1442
    {
1443
        $document = $root->ownerDocument;
1444
1445
        $conditions = $document->createElementNS(Constants::NS_SAML, 'saml:Conditions');
1446
        $root->appendChild($conditions);
1447
1448
        if ($this->notBefore !== null) {
1449
            $conditions->setAttribute('NotBefore', gmdate('Y-m-d\TH:i:s\Z', $this->notBefore));
1450
        }
1451
        if ($this->notOnOrAfter !== null) {
1452
            $conditions->setAttribute('NotOnOrAfter', gmdate('Y-m-d\TH:i:s\Z', $this->notOnOrAfter));
1453
        }
1454
1455
        if ($this->validAudiences !== null) {
1456
            $ar = $document->createElementNS(Constants::NS_SAML, 'saml:AudienceRestriction');
1457
            $conditions->appendChild($ar);
1458
1459
            Utils::addStrings($ar, Constants::NS_SAML, 'saml:Audience', false, $this->validAudiences);
1460
        }
1461
    }
1462
1463
1464
    /**
1465
     * Add a AuthnStatement-node to the assertion.
1466
     *
1467
     * @param \DOMElement $root The assertion element we should add the authentication statement to.
1468
     */
1469
    private function addAuthnStatement(\DOMElement $root)
1470
    {
1471
        if ($this->authnInstant === null ||
1472
            (
1473
                $this->authnContextClassRef === null &&
1474
                $this->authnContextDecl === null &&
1475
                $this->authnContextDeclRef === null
1476
            )
1477
        ) {
1478
            /* No authentication context or AuthnInstant => no authentication statement. */
1479
1480
            return;
1481
        }
1482
1483
        $document = $root->ownerDocument;
1484
1485
        $authnStatementEl = $document->createElementNS(Constants::NS_SAML, 'saml:AuthnStatement');
1486
        $root->appendChild($authnStatementEl);
1487
1488
        $authnStatementEl->setAttribute('AuthnInstant', gmdate('Y-m-d\TH:i:s\Z', $this->authnInstant));
1489
1490
        if ($this->sessionNotOnOrAfter !== null) {
1491
            $authnStatementEl->setAttribute('SessionNotOnOrAfter', gmdate('Y-m-d\TH:i:s\Z', $this->sessionNotOnOrAfter));
1492
        }
1493
        if ($this->sessionIndex !== null) {
1494
            $authnStatementEl->setAttribute('SessionIndex', $this->sessionIndex);
1495
        }
1496
1497
        $authnContextEl = $document->createElementNS(Constants::NS_SAML, 'saml:AuthnContext');
1498
        $authnStatementEl->appendChild($authnContextEl);
1499
1500
        if (!empty($this->authnContextClassRef)) {
1501
            Utils::addString(
1502
                $authnContextEl,
1503
                Constants::NS_SAML,
1504
                'saml:AuthnContextClassRef',
1505
                $this->authnContextClassRef
1506
            );
1507
        }
1508
        if (!empty($this->authnContextDecl)) {
1509
            $this->authnContextDecl->toXML($authnContextEl);
1510
        }
1511
        if (!empty($this->authnContextDeclRef)) {
1512
            Utils::addString(
1513
                $authnContextEl,
1514
                Constants::NS_SAML,
1515
                'saml:AuthnContextDeclRef',
1516
                $this->authnContextDeclRef
0 ignored issues
show
Documentation introduced by
$this->authnContextDeclRef is of type object<SAML2\XML\Chunk>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1517
            );
1518
        }
1519
1520
        Utils::addStrings(
1521
            $authnContextEl,
1522
            Constants::NS_SAML,
1523
            'saml:AuthenticatingAuthority',
1524
            false,
1525
            $this->AuthenticatingAuthority
1526
        );
1527
    }
1528
1529
1530
    /**
1531
     * Add an AttributeStatement-node to the assertion.
1532
     *
1533
     * @param \DOMElement $root The assertion element we should add the subject to.
1534
     */
1535
    private function addAttributeStatement(\DOMElement $root)
1536
    {
1537
        if (empty($this->attributes)) {
1538
            return;
1539
        }
1540
1541
        $document = $root->ownerDocument;
1542
1543
        $attributeStatement = $document->createElementNS(Constants::NS_SAML, 'saml:AttributeStatement');
1544
        $root->appendChild($attributeStatement);
1545
1546
        foreach ($this->attributes as $name => $values) {
1547
            $attribute = $document->createElementNS(Constants::NS_SAML, 'saml:Attribute');
1548
            $attributeStatement->appendChild($attribute);
1549
            $attribute->setAttribute('Name', $name);
1550
1551
            if ($this->nameFormat !== Constants::NAMEFORMAT_UNSPECIFIED) {
1552
                $attribute->setAttribute('NameFormat', $this->nameFormat);
1553
            }
1554
1555
            // make sure eduPersonTargetedID can be handled properly as a NameID
1556
            if ($name === Constants::EPTI_URN_MACE || $name === Constants::EPTI_URN_OID) {
1557
                foreach ($values as $eptiValue) {
1558
                    $attributeValue = $document->createElementNS(Constants::NS_SAML, 'saml:AttributeValue');
1559
                    $attribute->appendChild($attributeValue);
1560
                    if ($eptiValue instanceof XML\saml\NameID) {
1561
                        $eptiValue->toXML($attributeValue);
1562
                    } elseif ($eptiValue instanceof \DOMNodeList) {
1563
                        $node = $root->ownerDocument->importNode($eptiValue->item(0), true);
1564
                        $attributeValue->appendChild($node);
1565
                    } else {
1566
                        $attributeValue->textContent = $eptiValue;
1567
                    }
1568
                }
1569
1570
                continue;
1571
            }
1572
1573
            // get value type(s) for the current attribute
1574
            if (is_array($this->attributesValueTypes) && array_key_exists($name, $this->attributesValueTypes)) {
1575
                $valueTypes = $this->attributesValueTypes[$name];
1576
                if (is_array($valueTypes) && count($valueTypes) != count($values)) {
1577
                    throw new \Exception('Array of value types and array of values have different size for attribute '. var_export($name, true));
1578
                }
1579
            } else {
1580
                // if no type(s), default behaviour
1581
                $valueTypes = null;
1582
            }
1583
1584
            $vidx = -1;
1585
            foreach ($values as $value) {
1586
                $vidx++;
1587
1588
                // try to get type from current types
1589
                $type = null;
1590
                if (!is_null($valueTypes)) {
1591
                    if (is_array($valueTypes)) {
1592
                        $type = $valueTypes[$vidx];
1593
                    } else {
1594
                        $type = $valueTypes;
1595
                    }
1596
                }
1597
1598
                // if no type get from types, use default behaviour
1599 View Code Duplication
                if (is_null($type)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1600
                    if (is_string($value)) {
1601
                        $type = 'xs:string';
1602
                    } elseif (is_int($value)) {
1603
                        $type = 'xs:integer';
1604
                    } else {
1605
                        $type = null;
1606
                    }
1607
                }
1608
1609
                $attributeValue = $document->createElementNS(Constants::NS_SAML, 'saml:AttributeValue');
1610
                $attribute->appendChild($attributeValue);
1611
                if ($type !== null) {
1612
                    $attributeValue->setAttributeNS(Constants::NS_XSI, 'xsi:type', $type);
1613
                }
1614
                if (is_null($value)) {
1615
                    $attributeValue->setAttributeNS(Constants::NS_XSI, 'xsi:nil', 'true');
1616
                }
1617
1618 View Code Duplication
                if ($value instanceof \DOMNodeList) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1619
                    for ($i = 0; $i < $value->length; $i++) {
1620
                        $node = $document->importNode($value->item($i), true);
1621
                        $attributeValue->appendChild($node);
1622
                    }
1623
                } else {
1624
                    $attributeValue->appendChild($document->createTextNode($value));
1625
                }
1626
            }
1627
        }
1628
    }
1629
1630
1631
    /**
1632
     * Add an EncryptedAttribute Statement-node to the assertion.
1633
     *
1634
     * @param \DOMElement $root The assertion element we should add the Encrypted Attribute Statement to.
1635
     */
1636
    private function addEncryptedAttributeStatement(\DOMElement $root)
1637
    {
1638
        if ($this->requiredEncAttributes === false) {
1639
            return;
1640
        }
1641
1642
        $document = $root->ownerDocument;
1643
1644
        $attributeStatement = $document->createElementNS(Constants::NS_SAML, 'saml:AttributeStatement');
1645
        $root->appendChild($attributeStatement);
1646
1647
        foreach ($this->attributes as $name => $values) {
1648
            $document2 = DOMDocumentFactory::create();
1649
            $attribute = $document2->createElementNS(Constants::NS_SAML, 'saml:Attribute');
1650
            $attribute->setAttribute('Name', $name);
1651
            $document2->appendChild($attribute);
1652
1653
            if ($this->nameFormat !== Constants::NAMEFORMAT_UNSPECIFIED) {
1654
                $attribute->setAttribute('NameFormat', $this->nameFormat);
1655
            }
1656
1657
            foreach ($values as $value) {
1658 View Code Duplication
                if (is_string($value)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1659
                    $type = 'xs:string';
1660
                } elseif (is_int($value)) {
1661
                    $type = 'xs:integer';
1662
                } else {
1663
                    $type = null;
1664
                }
1665
1666
                $attributeValue = $document2->createElementNS(Constants::NS_SAML, 'saml:AttributeValue');
1667
                $attribute->appendChild($attributeValue);
1668
                if ($type !== null) {
1669
                    $attributeValue->setAttributeNS(Constants::NS_XSI, 'xsi:type', $type);
1670
                }
1671
1672 View Code Duplication
                if ($value instanceof \DOMNodeList) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1673
                    for ($i = 0; $i < $value->length; $i++) {
1674
                        $node = $document2->importNode($value->item($i), true);
1675
                        $attributeValue->appendChild($node);
1676
                    }
1677
                } else {
1678
                    $attributeValue->appendChild($document2->createTextNode($value));
1679
                }
1680
            }
1681
            /*Once the attribute nodes are built, the are encrypted*/
1682
            $EncAssert = new XMLSecEnc();
1683
            $EncAssert->setNode($document2->documentElement);
1684
            $EncAssert->type = 'http://www.w3.org/2001/04/xmlenc#Element';
1685
            /*
1686
             * Attributes are encrypted with a session key and this one with
1687
             * $EncryptionKey
1688
             */
1689
            $symmetricKey = new XMLSecurityKey(XMLSecurityKey::AES256_CBC);
1690
            $symmetricKey->generateSessionKey();
1691
            $EncAssert->encryptKey($this->encryptionKey, $symmetricKey);
0 ignored issues
show
Bug introduced by
It seems like $this->encryptionKey can be null; however, encryptKey() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1692
            $EncrNode = $EncAssert->encryptNode($symmetricKey);
1693
1694
            $EncAttribute = $document->createElementNS(Constants::NS_SAML, 'saml:EncryptedAttribute');
1695
            $attributeStatement->appendChild($EncAttribute);
1696
            $n = $document->importNode($EncrNode, true);
1697
            $EncAttribute->appendChild($n);
1698
        }
1699
    }
1700
}
1701