Completed
Push — master ( 4e360d...80bc96 )
by Daan van
07:27
created

Assertion::getSignatureKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace SAML2;
4
5
use RobRichards\XMLSecLibs\XMLSecEnc;
6
use RobRichards\XMLSecLibs\XMLSecurityKey;
7
use SAML2\Utilities\Temporal;
8
use SAML2\XML\Chunk;
9
use SAML2\XML\saml\SubjectConfirmation;
10
11
/**
12
 * Class representing a SAML 2 assertion.
13
 *
14
 * @package SimpleSAMLphp
15
 */
16
class Assertion implements SignedElement
17
{
18
    /**
19
     * The identifier of this assertion.
20
     *
21
     * @var string
22
     */
23
    private $id;
24
25
    /**
26
     * The issue timestamp of this assertion, as an UNIX timestamp.
27
     *
28
     * @var int
29
     */
30
    private $issueInstant;
31
32
    /**
33
     * The entity id of the issuer of this assertion.
34
     *
35
     * @var string
36
     */
37
    private $issuer;
38
39
    /**
40
     * The NameId of the subject in the assertion.
41
     *
42
     * If the NameId is null, no subject was included in the assertion.
43
     *
44
     * @var array|null
45
     */
46
    private $nameId;
47
48
    /**
49
     * The encrypted NameId of the subject.
50
     *
51
     * If this is not null, the NameId needs decryption before it can be accessed.
52
     *
53
     * @var \DOMElement|null
54
     */
55
    private $encryptedNameId;
56
57
    /**
58
     * The encrypted Attributes.
59
     *
60
     * If this is not null, these Attributes need decryption before they can be accessed.
61
     *
62
     * @var \DOMElement[]|null
63
     */
64
    private $encryptedAttributes;
65
66
    /**
67
     * Private key we should use to encrypt the attributes.
68
     *
69
     * @var XMLSecurityKey|null
70
     */
71
    private $encryptionKey;
72
73
     /**
74
     * The earliest time this assertion is valid, as an UNIX timestamp.
75
     *
76
     * @var int
77
     */
78
    private $notBefore;
79
80
    /**
81
     * The time this assertion expires, as an UNIX timestamp.
82
     *
83
     * @var int
84
     */
85
    private $notOnOrAfter;
86
87
    /**
88
     * The set of audiences that are allowed to receive this assertion.
89
     *
90
     * This is an array of valid service providers.
91
     *
92
     * If no restrictions on the audience are present, this variable contains null.
93
     *
94
     * @var array|null
95
     */
96
    private $validAudiences;
97
98
    /**
99
     * The session expiration timestamp.
100
     *
101
     * @var int|null
102
     */
103
    private $sessionNotOnOrAfter;
104
105
    /**
106
     * The session index for this user on the IdP.
107
     *
108
     * Contains null if no session index is present.
109
     *
110
     * @var string|null
111
     */
112
    private $sessionIndex;
113
114
    /**
115
     * The timestamp the user was authenticated, as an UNIX timestamp.
116
     *
117
     * @var int
118
     */
119
    private $authnInstant;
120
121
    /**
122
     * The authentication context reference for this assertion.
123
     *
124
     * @var string|null
125
     */
126
    private $authnContextClassRef;
127
128
    /**
129
     * Authentication context declaration provided by value.
130
     *
131
     * See:
132
     * @url http://docs.oasis-open.org/security/saml/v2.0/saml-authn-context-2.0-os.pdf
133
     *
134
     * @var \SAML2\XML\Chunk
135
     */
136
    private $authnContextDecl;
137
138
    /**
139
     * URI reference that identifies an authentication context declaration.
140
     *
141
     * The URI reference MAY directly resolve into an XML document containing the referenced declaration.
142
     *
143
     * @var \SAML2\XML\Chunk
144
     */
145
    private $authnContextDeclRef;
146
147
    /**
148
     * The list of AuthenticatingAuthorities for this assertion.
149
     *
150
     * @var array
151
     */
152
    private $AuthenticatingAuthority;
153
154
    /**
155
     * The attributes, as an associative array.
156
     *
157
     * @var \DOMElement[]
158
     */
159
    private $attributes;
160
161
    /**
162
     * The NameFormat used on all attributes.
163
     *
164
     * If more than one NameFormat is used, this will contain
165
     * the unspecified nameformat.
166
     *
167
     * @var string
168
     */
169
    private $nameFormat;
170
171
    /**
172
     * The private key we should use to sign the assertion.
173
     *
174
     * The private key can be null, in which case the assertion is sent unsigned.
175
     *
176
     * @var XMLSecurityKey|null
177
     */
178
    private $signatureKey;
179
180
    /**
181
     * List of certificates that should be included in the assertion.
182
     *
183
     * @var array
184
     */
185
    private $certificates;
186
187
    /**
188
     * The data needed to verify the signature.
189
     *
190
     * @var array|null
191
     */
192
    private $signatureData;
193
194
    /**
195
     * Boolean that indicates if attributes are encrypted in the
196
     * assertion or not.
197
     *
198
     * @var boolean
199
     */
200
    private $requiredEncAttributes;
201
202
    /**
203
     * The SubjectConfirmation elements of the Subject in the assertion.
204
     *
205
     * @var \SAML2\XML\saml\SubjectConfirmation[].
206
     */
207
    private $SubjectConfirmation;
208
209
    /**
210
     * @var bool
211
     */
212
    protected $wasSignedAtConstruction = false;
213
214
    /**
215
     * Constructor for SAML 2 assertions.
216
     *
217
     * @param \DOMElement|null $xml The input assertion.
218
     * @throws \Exception
219
     */
220
    public function __construct(\DOMElement $xml = null)
221
    {
222
        $this->id = Utils::getContainer()->generateId();
223
        $this->issueInstant = Temporal::getTime();
224
        $this->issuer = '';
225
        $this->authnInstant = Temporal::getTime();
226
        $this->attributes = array();
227
        $this->nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
228
        $this->certificates = array();
229
        $this->AuthenticatingAuthority = array();
230
        $this->SubjectConfirmation = array();
231
232
        if ($xml === null) {
233
            return;
234
        }
235
236
        if (!$xml->hasAttribute('ID')) {
237
            throw new \Exception('Missing ID attribute on SAML assertion.');
238
        }
239
        $this->id = $xml->getAttribute('ID');
240
241
        if ($xml->getAttribute('Version') !== '2.0') {
242
            /* Currently a very strict check. */
243
            throw new \Exception('Unsupported version: ' . $xml->getAttribute('Version'));
244
        }
245
246
        $this->issueInstant = Utils::xsDateTimeToTimestamp($xml->getAttribute('IssueInstant'));
247
248
        $issuer = Utils::xpQuery($xml, './saml_assertion:Issuer');
249
        if (empty($issuer)) {
250
            throw new \Exception('Missing <saml:Issuer> in assertion.');
251
        }
252
        $this->issuer = trim($issuer[0]->textContent);
253
254
        $this->parseSubject($xml);
255
        $this->parseConditions($xml);
256
        $this->parseAuthnStatement($xml);
257
        $this->parseAttributes($xml);
258
        $this->parseEncryptedAttributes($xml);
259
        $this->parseSignature($xml);
260
    }
261
262
    /**
263
     * Parse subject in assertion.
264
     *
265
     * @param \DOMElement $xml The assertion XML element.
266
     * @throws \Exception
267
     */
268 View Code Duplication
    private function parseSubject(\DOMElement $xml)
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...
269
    {
270
        $subject = Utils::xpQuery($xml, './saml_assertion:Subject');
271
        if (empty($subject)) {
272
            /* No Subject node. */
273
274
            return;
275
        } elseif (count($subject) > 1) {
276
            throw new \Exception('More than one <saml:Subject> in <saml:Assertion>.');
277
        }
278
        $subject = $subject[0];
279
280
        $nameId = Utils::xpQuery(
281
            $subject,
282
            './saml_assertion:NameID | ./saml_assertion:EncryptedID/xenc:EncryptedData'
283
        );
284
        if (empty($nameId)) {
285
            throw new \Exception('Missing <saml:NameID> or <saml:EncryptedID> in <saml:Subject>.');
286
        } elseif (count($nameId) > 1) {
287
            throw new \Exception('More than one <saml:NameID> or <saml:EncryptedD> in <saml:Subject>.');
288
        }
289
        $nameId = $nameId[0];
290
        if ($nameId->localName === 'EncryptedData') {
291
            /* The NameID element is encrypted. */
292
            $this->encryptedNameId = $nameId;
293
        } else {
294
            $this->nameId = Utils::parseNameId($nameId);
295
        }
296
297
        $subjectConfirmation = Utils::xpQuery($subject, './saml_assertion:SubjectConfirmation');
298
        if (empty($subjectConfirmation)) {
299
            throw new \Exception('Missing <saml:SubjectConfirmation> in <saml:Subject>.');
300
        }
301
302
        foreach ($subjectConfirmation as $sc) {
303
            $this->SubjectConfirmation[] = new SubjectConfirmation($sc);
304
        }
305
    }
306
307
    /**
308
     * Parse conditions in assertion.
309
     *
310
     * @param \DOMElement $xml The assertion XML element.
311
     * @throws \Exception
312
     */
313
    private function parseConditions(\DOMElement $xml)
314
    {
315
        $conditions = Utils::xpQuery($xml, './saml_assertion:Conditions');
316
        if (empty($conditions)) {
317
            /* No <saml:Conditions> node. */
318
319
            return;
320
        } elseif (count($conditions) > 1) {
321
            throw new \Exception('More than one <saml:Conditions> in <saml:Assertion>.');
322
        }
323
        $conditions = $conditions[0];
324
325 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...
326
            $notBefore = Utils::xsDateTimeToTimestamp($conditions->getAttribute('NotBefore'));
327
            if ($this->notBefore === null || $this->notBefore < $notBefore) {
328
                $this->notBefore = $notBefore;
329
            }
330
        }
331 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...
332
            $notOnOrAfter = Utils::xsDateTimeToTimestamp($conditions->getAttribute('NotOnOrAfter'));
333
            if ($this->notOnOrAfter === null || $this->notOnOrAfter > $notOnOrAfter) {
334
                $this->notOnOrAfter = $notOnOrAfter;
335
            }
336
        }
337
338
        for ($node = $conditions->firstChild; $node !== null; $node = $node->nextSibling) {
339
            if ($node instanceof \DOMText) {
340
                continue;
341
            }
342 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...
343
                throw new \Exception('Unknown namespace of condition: ' . var_export($node->namespaceURI, true));
344
            }
345
            switch ($node->localName) {
346
                case 'AudienceRestriction':
347
                    $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...
348
                    if ($this->validAudiences === null) {
349
                        /* The first (and probably last) AudienceRestriction element. */
350
                        $this->validAudiences = $audiences;
351
                    } else {
352
                        /*
353
                         * The set of AudienceRestriction are ANDed together, so we need
354
                         * the subset that are present in all of them.
355
                         */
356
                        $this->validAudiences = array_intersect($this->validAudiences, $audiences);
357
                    }
358
                    break;
359
                case 'OneTimeUse':
360
                    /* Currently ignored. */
361
                    break;
362
                case 'ProxyRestriction':
363
                    /* Currently ignored. */
364
                    break;
365
                default:
366
                    throw new \Exception('Unknown condition: ' . var_export($node->localName, true));
367
            }
368
        }
369
    }
370
371
    /**
372
     * Parse AuthnStatement in assertion.
373
     *
374
     * @param \DOMElement $xml The assertion XML element.
375
     * @throws \Exception
376
     */
377
    private function parseAuthnStatement(\DOMElement $xml)
378
    {
379
        $authnStatements = Utils::xpQuery($xml, './saml_assertion:AuthnStatement');
380
        if (empty($authnStatements)) {
381
            $this->authnInstant = null;
382
383
            return;
384
        } elseif (count($authnStatements) > 1) {
385
            throw new \Exception('More that one <saml:AuthnStatement> in <saml:Assertion> not supported.');
386
        }
387
        $authnStatement = $authnStatements[0];
388
389
        if (!$authnStatement->hasAttribute('AuthnInstant')) {
390
            throw new \Exception('Missing required AuthnInstant attribute on <saml:AuthnStatement>.');
391
        }
392
        $this->authnInstant = Utils::xsDateTimeToTimestamp($authnStatement->getAttribute('AuthnInstant'));
393
394
        if ($authnStatement->hasAttribute('SessionNotOnOrAfter')) {
395
            $this->sessionNotOnOrAfter = Utils::xsDateTimeToTimestamp($authnStatement->getAttribute('SessionNotOnOrAfter'));
396
        }
397
398
        if ($authnStatement->hasAttribute('SessionIndex')) {
399
            $this->sessionIndex = $authnStatement->getAttribute('SessionIndex');
400
        }
401
402
        $this->parseAuthnContext($authnStatement);
403
    }
404
405
    /**
406
     * Parse AuthnContext in AuthnStatement.
407
     *
408
     * @param \DOMElement $authnStatementEl
409
     * @throws \Exception
410
     */
411
    private function parseAuthnContext(\DOMElement $authnStatementEl)
412
    {
413
        // Get the AuthnContext element
414
        $authnContexts = Utils::xpQuery($authnStatementEl, './saml_assertion:AuthnContext');
415
        if (count($authnContexts) > 1) {
416
            throw new \Exception('More than one <saml:AuthnContext> in <saml:AuthnStatement>.');
417
        } elseif (empty($authnContexts)) {
418
            throw new \Exception('Missing required <saml:AuthnContext> in <saml:AuthnStatement>.');
419
        }
420
        $authnContextEl = $authnContexts[0];
421
422
        // Get the AuthnContextDeclRef (if available)
423
        $authnContextDeclRefs = Utils::xpQuery($authnContextEl, './saml_assertion:AuthnContextDeclRef');
424
        if (count($authnContextDeclRefs) > 1) {
425
            throw new \Exception(
426
                'More than one <saml:AuthnContextDeclRef> found?'
427
            );
428
        } elseif (count($authnContextDeclRefs) === 1) {
429
            $this->setAuthnContextDeclRef(trim($authnContextDeclRefs[0]->textContent));
430
        }
431
432
        // Get the AuthnContextDecl (if available)
433
        $authnContextDecls = Utils::xpQuery($authnContextEl, './saml_assertion:AuthnContextDecl');
434
        if (count($authnContextDecls) > 1) {
435
            throw new \Exception(
436
                'More than one <saml:AuthnContextDecl> found?'
437
            );
438
        } elseif (count($authnContextDecls) === 1) {
439
            $this->setAuthnContextDecl(new Chunk($authnContextDecls[0]));
440
        }
441
442
        // Get the AuthnContextClassRef (if available)
443
        $authnContextClassRefs = Utils::xpQuery($authnContextEl, './saml_assertion:AuthnContextClassRef');
444
        if (count($authnContextClassRefs) > 1) {
445
            throw new \Exception('More than one <saml:AuthnContextClassRef> in <saml:AuthnContext>.');
446
        } elseif (count($authnContextClassRefs) === 1) {
447
            $this->setAuthnContextClassRef(trim($authnContextClassRefs[0]->textContent));
448
        }
449
450
        // Constraint from XSD: MUST have one of the three
451
        if (empty($this->authnContextClassRef) && empty($this->authnContextDecl) && empty($this->authnContextDeclRef)) {
452
            throw new \Exception(
453
                'Missing either <saml:AuthnContextClassRef> or <saml:AuthnContextDeclRef> or <saml:AuthnContextDecl>'
454
            );
455
        }
456
457
        $this->AuthenticatingAuthority = Utils::extractStrings(
458
            $authnContextEl,
459
            Constants::NS_SAML,
460
            'AuthenticatingAuthority'
461
        );
462
    }
463
464
    /**
465
     * Parse attribute statements in assertion.
466
     *
467
     * @param \DOMElement $xml The XML element with the assertion.
468
     * @throws \Exception
469
     */
470
    private function parseAttributes(\DOMElement $xml)
471
    {
472
        $firstAttribute = true;
473
        $attributes = Utils::xpQuery($xml, './saml_assertion:AttributeStatement/saml_assertion:Attribute');
474 View Code Duplication
        foreach ($attributes as $attribute) {
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...
475
            if (!$attribute->hasAttribute('Name')) {
476
                throw new \Exception('Missing name on <saml:Attribute> element.');
477
            }
478
            $name = $attribute->getAttribute('Name');
479
480
            if ($attribute->hasAttribute('NameFormat')) {
481
                $nameFormat = $attribute->getAttribute('NameFormat');
482
            } else {
483
                $nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
484
            }
485
486
            if ($firstAttribute) {
487
                $this->nameFormat = $nameFormat;
488
                $firstAttribute = false;
489
            } else {
490
                if ($this->nameFormat !== $nameFormat) {
491
                    $this->nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
492
                }
493
            }
494
495
            if (!array_key_exists($name, $this->attributes)) {
496
                $this->attributes[$name] = array();
497
            }
498
499
            $values = Utils::xpQuery($attribute, './saml_assertion:AttributeValue');
500
            foreach ($values as $value) {
501
                $this->attributes[$name][] = trim($value->textContent);
502
            }
503
        }
504
    }
505
506
    /**
507
     * Parse encrypted attribute statements in assertion.
508
     *
509
     * @param \DOMElement $xml The XML element with the assertion.
510
     */
511
    private function parseEncryptedAttributes(\DOMElement $xml)
512
    {
513
        $this->encryptedAttributes = Utils::xpQuery(
514
            $xml,
515
            './saml_assertion:AttributeStatement/saml_assertion:EncryptedAttribute'
516
        );
517
    }
518
519
    /**
520
     * Parse signature on assertion.
521
     *
522
     * @param \DOMElement $xml The assertion XML element.
523
     */
524
    private function parseSignature(\DOMElement $xml)
525
    {
526
        /* Validate the signature element of the message. */
527
        $sig = Utils::validateElement($xml);
528
        if ($sig !== false) {
529
            $this->wasSignedAtConstruction = true;
530
            $this->certificates = $sig['Certificates'];
531
            $this->signatureData = $sig;
532
        }
533
    }
534
535
    /**
536
     * Validate this assertion against a public key.
537
     *
538
     * If no signature was present on the assertion, we will return false.
539
     * Otherwise, true will be returned. An exception is thrown if the
540
     * signature validation fails.
541
     *
542
     * @param  XMLSecurityKey $key The key we should check against.
543
     * @return boolean        true if successful, false if it is unsigned.
544
     */
545
    public function validate(XMLSecurityKey $key)
546
    {
547
        assert('$key->type === XMLSecurityKey::RSA_SHA1');
548
549
        if ($this->signatureData === null) {
550
            return false;
551
        }
552
553
        Utils::validateSignature($this->signatureData, $key);
554
555
        return true;
556
    }
557
558
    /**
559
     * Retrieve the identifier of this assertion.
560
     *
561
     * @return string The identifier of this assertion.
562
     */
563
    public function getId()
564
    {
565
        return $this->id;
566
    }
567
568
    /**
569
     * Set the identifier of this assertion.
570
     *
571
     * @param string $id The new identifier of this assertion.
572
     */
573
    public function setId($id)
574
    {
575
        assert('is_string($id)');
576
577
        $this->id = $id;
578
    }
579
580
    /**
581
     * Retrieve the issue timestamp of this assertion.
582
     *
583
     * @return int The issue timestamp of this assertion, as an UNIX timestamp.
584
     */
585
    public function getIssueInstant()
586
    {
587
        return $this->issueInstant;
588
    }
589
590
    /**
591
     * Set the issue timestamp of this assertion.
592
     *
593
     * @param int $issueInstant The new issue timestamp of this assertion, as an UNIX timestamp.
594
     */
595
    public function setIssueInstant($issueInstant)
596
    {
597
        assert('is_int($issueInstant)');
598
599
        $this->issueInstant = $issueInstant;
600
    }
601
602
    /**
603
     * Retrieve the issuer if this assertion.
604
     *
605
     * @return string The issuer of this assertion.
606
     */
607
    public function getIssuer()
608
    {
609
        return $this->issuer;
610
    }
611
612
    /**
613
     * Set the issuer of this message.
614
     *
615
     * @param string $issuer The new issuer of this assertion.
616
     */
617
    public function setIssuer($issuer)
618
    {
619
        assert('is_string($issuer)');
620
621
        $this->issuer = $issuer;
622
    }
623
624
    /**
625
     * Retrieve the NameId of the subject in the assertion.
626
     *
627
     * The returned NameId is in the format used by \SAML2\Utils::addNameId().
628
     *
629
     * @see \SAML2\Utils::addNameId()
630
     * @return array|null The name identifier of the assertion.
631
     * @throws \Exception
632
     */
633
    public function getNameId()
634
    {
635
        if ($this->encryptedNameId !== null) {
636
            throw new \Exception('Attempted to retrieve encrypted NameID without decrypting it first.');
637
        }
638
639
        return $this->nameId;
640
    }
641
642
    /**
643
     * Set the NameId of the subject in the assertion.
644
     *
645
     * The NameId must be in the format accepted by \SAML2\Utils::addNameId().
646
     *
647
     * @see \SAML2\Utils::addNameId()
648
     * @param array|null $nameId The name identifier of the assertion.
649
     */
650
    public function setNameId($nameId)
651
    {
652
        assert('is_array($nameId) || is_null($nameId)');
653
654
        $this->nameId = $nameId;
655
    }
656
657
    /**
658
     * Check whether the NameId is encrypted.
659
     *
660
     * @return true if the NameId is encrypted, false if not.
661
     */
662
    public function isNameIdEncrypted()
663
    {
664
        return $this->encryptedNameId !== null;
665
    }
666
667
    /**
668
     * Encrypt the NameID in the Assertion.
669
     *
670
     * @param XMLSecurityKey $key The encryption key.
671
     */
672 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...
673
    {
674
        /* First create a XML representation of the NameID. */
675
        $doc = DOMDocumentFactory::create();
676
        $root = $doc->createElement('root');
677
        $doc->appendChild($root);
678
        Utils::addNameId($root, $this->nameId);
0 ignored issues
show
Bug introduced by
It seems like $this->nameId can also be of type null; however, SAML2\Utils::addNameId() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
679
        $nameId = $root->firstChild;
680
681
        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...
682
683
        /* Encrypt the NameID. */
684
        $enc = new XMLSecEnc();
685
        $enc->setNode($nameId);
686
        // @codingStandardsIgnoreStart
687
        $enc->type = XMLSecEnc::Element;
688
        // @codingStandardsIgnoreEnd
689
690
        $symmetricKey = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
691
        $symmetricKey->generateSessionKey();
692
        $enc->encryptKey($key, $symmetricKey);
693
694
        $this->encryptedNameId = $enc->encryptNode($symmetricKey);
0 ignored issues
show
Documentation Bug introduced by
It seems like $enc->encryptNode($symmetricKey) of type object<RobRichards\XMLSecLibs\DOMElement> is incompatible with the declared type object<DOMElement>|null of property $encryptedNameId.

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...
695
        $this->nameId = null;
696
    }
697
698
    /**
699
     * Decrypt the NameId of the subject in the assertion.
700
     *
701
     * @param XMLSecurityKey $key       The decryption key.
702
     * @param array          $blacklist Blacklisted decryption algorithms.
703
     */
704 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...
705
    {
706
        if ($this->encryptedNameId === null) {
707
            /* No NameID to decrypt. */
708
709
            return;
710
        }
711
712
        $nameId = Utils::decryptElement($this->encryptedNameId, $key, $blacklist);
713
        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...
714
        $this->nameId = Utils::parseNameId($nameId);
715
716
        $this->encryptedNameId = null;
717
    }
718
719
    /**
720
     * Did this Assertion contain encrypted Attributes?
721
     *
722
     * @return bool
723
     */
724
    public function hasEncryptedAttributes()
725
    {
726
        return $this->encryptedAttributes !== null;
727
    }
728
729
    /**
730
     * Decrypt the assertion attributes.
731
     *
732
     * @param XMLSecurityKey $key
733
     * @param array $blacklist
734
     * @throws \Exception
735
     */
736
    public function decryptAttributes(XMLSecurityKey $key, array $blacklist = array())
737
    {
738
        if ($this->encryptedAttributes === null) {
739
            return;
740
        }
741
        $firstAttribute = true;
742
        $attributes = $this->encryptedAttributes;
743
        foreach ($attributes as $attributeEnc) {
744
            /*Decrypt node <EncryptedAttribute>*/
745
            $attribute = Utils::decryptElement(
746
                $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...
747
                $key,
748
                $blacklist
749
            );
750
751
            if (!$attribute->hasAttribute('Name')) {
752
                throw new \Exception('Missing name on <saml:Attribute> element.');
753
            }
754
            $name = $attribute->getAttribute('Name');
755
756
            if ($attribute->hasAttribute('NameFormat')) {
757
                $nameFormat = $attribute->getAttribute('NameFormat');
758
            } else {
759
                $nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
760
            }
761
762
            if ($firstAttribute) {
763
                $this->nameFormat = $nameFormat;
764
                $firstAttribute = false;
765
            } else {
766
                if ($this->nameFormat !== $nameFormat) {
767
                    $this->nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
768
                }
769
            }
770
771
            if (!array_key_exists($name, $this->attributes)) {
772
                $this->attributes[$name] = array();
773
            }
774
775
            $values = Utils::xpQuery($attribute, './saml_assertion:AttributeValue');
776
            foreach ($values as $value) {
777
                $this->attributes[$name][] = trim($value->textContent);
778
            }
779
        }
780
    }
781
782
    /**
783
     * Retrieve the earliest timestamp this assertion is valid.
784
     *
785
     * This function returns null if there are no restrictions on how early the
786
     * assertion can be used.
787
     *
788
     * @return int|null The earliest timestamp this assertion is valid.
789
     */
790
    public function getNotBefore()
791
    {
792
        return $this->notBefore;
793
    }
794
795
    /**
796
     * Set the earliest timestamp this assertion can be used.
797
     *
798
     * Set this to null if no limit is required.
799
     *
800
     * @param int|null $notBefore The earliest timestamp this assertion is valid.
801
     */
802
    public function setNotBefore($notBefore)
803
    {
804
        assert('is_int($notBefore) || is_null($notBefore)');
805
806
        $this->notBefore = $notBefore;
807
    }
808
809
    /**
810
     * Retrieve the expiration timestamp of this assertion.
811
     *
812
     * This function returns null if there are no restrictions on how
813
     * late the assertion can be used.
814
     *
815
     * @return int|null The latest timestamp this assertion is valid.
816
     */
817
    public function getNotOnOrAfter()
818
    {
819
        return $this->notOnOrAfter;
820
    }
821
822
    /**
823
     * Set the expiration timestamp of this assertion.
824
     *
825
     * Set this to null if no limit is required.
826
     *
827
     * @param int|null $notOnOrAfter The latest timestamp this assertion is valid.
828
     */
829
    public function setNotOnOrAfter($notOnOrAfter)
830
    {
831
        assert('is_int($notOnOrAfter) || is_null($notOnOrAfter)');
832
833
        $this->notOnOrAfter = $notOnOrAfter;
834
    }
835
836
    /**
837
     * Set $EncryptedAttributes if attributes will send encrypted
838
     *
839
     * @param boolean $ea true to encrypt attributes in the assertion.
840
     */
841
    public function setEncryptedAttributes($ea)
842
    {
843
        $this->requiredEncAttributes = $ea;
844
    }
845
846
    /**
847
     * Retrieve the audiences that are allowed to receive this assertion.
848
     *
849
     * This may be null, in which case all audiences are allowed.
850
     *
851
     * @return array|null The allowed audiences.
852
     */
853
    public function getValidAudiences()
854
    {
855
        return $this->validAudiences;
856
    }
857
858
    /**
859
     * Set the audiences that are allowed to receive this assertion.
860
     *
861
     * This may be null, in which case all audiences are allowed.
862
     *
863
     * @param array|null $validAudiences The allowed audiences.
864
     */
865
    public function setValidAudiences(array $validAudiences = null)
866
    {
867
        $this->validAudiences = $validAudiences;
868
    }
869
870
    /**
871
     * Retrieve the AuthnInstant of the assertion.
872
     *
873
     * @return int|null The timestamp the user was authenticated, or NULL if the user isn't authenticated.
874
     */
875
    public function getAuthnInstant()
876
    {
877
        return $this->authnInstant;
878
    }
879
880
881
    /**
882
     * Set the AuthnInstant of the assertion.
883
     *
884
     * @param int|null $authnInstant Timestamp the user was authenticated, or NULL if we don't want an AuthnStatement.
885
     */
886
    public function setAuthnInstant($authnInstant)
887
    {
888
        assert('is_int($authnInstant) || is_null($authnInstant)');
889
890
        $this->authnInstant = $authnInstant;
891
    }
892
893
    /**
894
     * Retrieve the session expiration timestamp.
895
     *
896
     * This function returns null if there are no restrictions on the
897
     * session lifetime.
898
     *
899
     * @return int|null The latest timestamp this session is valid.
900
     */
901
    public function getSessionNotOnOrAfter()
902
    {
903
        return $this->sessionNotOnOrAfter;
904
    }
905
906
    /**
907
     * Set the session expiration timestamp.
908
     *
909
     * Set this to null if no limit is required.
910
     *
911
     * @param int|null $sessionNotOnOrAfter The latest timestamp this session is valid.
912
     */
913
    public function setSessionNotOnOrAfter($sessionNotOnOrAfter)
914
    {
915
        assert('is_int($sessionNotOnOrAfter) || is_null($sessionNotOnOrAfter)');
916
917
        $this->sessionNotOnOrAfter = $sessionNotOnOrAfter;
918
    }
919
920
    /**
921
     * Retrieve the session index of the user at the IdP.
922
     *
923
     * @return string|null The session index of the user at the IdP.
924
     */
925
    public function getSessionIndex()
926
    {
927
        return $this->sessionIndex;
928
    }
929
930
    /**
931
     * Set the session index of the user at the IdP.
932
     *
933
     * Note that the authentication context must be set before the
934
     * session index can be inluded in the assertion.
935
     *
936
     * @param string|null $sessionIndex The session index of the user at the IdP.
937
     */
938
    public function setSessionIndex($sessionIndex)
939
    {
940
        assert('is_string($sessionIndex) || is_null($sessionIndex)');
941
942
        $this->sessionIndex = $sessionIndex;
943
    }
944
945
    /**
946
     * Retrieve the authentication method used to authenticate the user.
947
     *
948
     * This will return null if no authentication statement was
949
     * included in the assertion.
950
     *
951
     * Note that this returns either the AuthnContextClassRef or the AuthnConextDeclRef, whose definition overlaps
952
     * but is slightly different (consult the specification for more information).
953
     * This was done to work around an old bug of Shibboleth ( https://bugs.internet2.edu/jira/browse/SIDP-187 ).
954
     * Should no longer be required, please use either getAuthnConextClassRef or getAuthnContextDeclRef.
955
     *
956
     * @deprecated use getAuthnContextClassRef
957
     * @return string|null The authentication method.
958
     */
959
    public function getAuthnContext()
960
    {
961
        if (!empty($this->authnContextClassRef)) {
962
            return $this->authnContextClassRef;
963
        }
964
        if (!empty($this->authnContextDeclRef)) {
965
            return $this->authnContextDeclRef;
966
        }
967
        return null;
968
    }
969
970
    /**
971
     * Set the authentication method used to authenticate the user.
972
     *
973
     * If this is set to null, no authentication statement will be
974
     * included in the assertion. The default is null.
975
     *
976
     * @deprecated use setAuthnContextClassRef
977
     * @param string|null $authnContext The authentication method.
978
     */
979
    public function setAuthnContext($authnContext)
980
    {
981
        $this->setAuthnContextClassRef($authnContext);
982
    }
983
984
    /**
985
     * Retrieve the authentication method used to authenticate the user.
986
     *
987
     * This will return null if no authentication statement was
988
     * included in the assertion.
989
     *
990
     * @return string|null The authentication method.
991
     */
992
    public function getAuthnContextClassRef()
993
    {
994
        return $this->authnContextClassRef;
995
    }
996
997
    /**
998
     * Set the authentication method used to authenticate the user.
999
     *
1000
     * If this is set to null, no authentication statement will be
1001
     * included in the assertion. The default is null.
1002
     *
1003
     * @param string|null $authnContextClassRef The authentication method.
1004
     */
1005
    public function setAuthnContextClassRef($authnContextClassRef)
1006
    {
1007
        assert('is_string($authnContextClassRef) || is_null($authnContextClassRef)');
1008
1009
        $this->authnContextClassRef = $authnContextClassRef;
1010
    }
1011
1012
    /**
1013
     * Set the authentication context declaration.
1014
     *
1015
     * @param \SAML2\XML\Chunk $authnContextDecl
1016
     * @throws \Exception
1017
     */
1018
    public function setAuthnContextDecl(Chunk $authnContextDecl)
1019
    {
1020
        if (!empty($this->authnContextDeclRef)) {
1021
            throw new \Exception(
1022
                'AuthnContextDeclRef is already registered! May only have either a Decl or a DeclRef, not both!'
1023
            );
1024
        }
1025
1026
        $this->authnContextDecl = $authnContextDecl;
1027
    }
1028
1029
    /**
1030
     * Get the authentication context declaration.
1031
     *
1032
     * See:
1033
     * @url http://docs.oasis-open.org/security/saml/v2.0/saml-authn-context-2.0-os.pdf
1034
     *
1035
     * @return \SAML2\XML\Chunk|null
1036
     */
1037
    public function getAuthnContextDecl()
1038
    {
1039
        return $this->authnContextDecl;
1040
    }
1041
1042
    /**
1043
     * Set the authentication context declaration reference.
1044
     *
1045
     * @param string $authnContextDeclRef
1046
     * @throws \Exception
1047
     */
1048
    public function setAuthnContextDeclRef($authnContextDeclRef)
1049
    {
1050
        if (!empty($this->authnContextDecl)) {
1051
            throw new \Exception(
1052
                'AuthnContextDecl is already registered! May only have either a Decl or a DeclRef, not both!'
1053
            );
1054
        }
1055
1056
        $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...
1057
    }
1058
1059
    /**
1060
     * Get the authentication context declaration reference.
1061
     * URI reference that identifies an authentication context declaration.
1062
     *
1063
     * The URI reference MAY directly resolve into an XML document containing the referenced declaration.
1064
     *
1065
     * @return string
1066
     */
1067
    public function getAuthnContextDeclRef()
1068
    {
1069
        return $this->authnContextDeclRef;
1070
    }
1071
1072
    /**
1073
     * Retrieve the AuthenticatingAuthority.
1074
     *
1075
     *
1076
     * @return array
1077
     */
1078
    public function getAuthenticatingAuthority()
1079
    {
1080
        return $this->AuthenticatingAuthority;
1081
    }
1082
1083
    /**
1084
     * Set the AuthenticatingAuthority
1085
     *
1086
     *
1087
     * @param array.
1088
     */
1089
    public function setAuthenticatingAuthority($authenticatingAuthority)
1090
    {
1091
        $this->AuthenticatingAuthority = $authenticatingAuthority;
1092
    }
1093
1094
    /**
1095
     * Retrieve all attributes.
1096
     *
1097
     * @return array All attributes, as an associative array.
1098
     */
1099
    public function getAttributes()
1100
    {
1101
        return $this->attributes;
1102
    }
1103
1104
    /**
1105
     * Replace all attributes.
1106
     *
1107
     * @param array $attributes All new attributes, as an associative array.
1108
     */
1109
    public function setAttributes(array $attributes)
1110
    {
1111
        $this->attributes = $attributes;
1112
    }
1113
1114
    /**
1115
     * Retrieve the NameFormat used on all attributes.
1116
     *
1117
     * If more than one NameFormat is used in the received attributes, this
1118
     * returns the unspecified NameFormat.
1119
     *
1120
     * @return string The NameFormat used on all attributes.
1121
     */
1122
    public function getAttributeNameFormat()
1123
    {
1124
        return $this->nameFormat;
1125
    }
1126
1127
    /**
1128
     * Set the NameFormat used on all attributes.
1129
     *
1130
     * @param string $nameFormat The NameFormat used on all attributes.
1131
     */
1132
    public function setAttributeNameFormat($nameFormat)
1133
    {
1134
        assert('is_string($nameFormat)');
1135
1136
        $this->nameFormat = $nameFormat;
1137
    }
1138
1139
    /**
1140
     * Retrieve the SubjectConfirmation elements we have in our Subject element.
1141
     *
1142
     * @return array Array of \SAML2\XML\saml\SubjectConfirmation elements.
1143
     */
1144
    public function getSubjectConfirmation()
1145
    {
1146
        return $this->SubjectConfirmation;
1147
    }
1148
1149
    /**
1150
     * Set the SubjectConfirmation elements that should be included in the assertion.
1151
     *
1152
     * @param array $SubjectConfirmation Array of \SAML2\XML\saml\SubjectConfirmation elements.
1153
     */
1154
    public function setSubjectConfirmation(array $SubjectConfirmation)
1155
    {
1156
        $this->SubjectConfirmation = $SubjectConfirmation;
1157
    }
1158
1159
    /**
1160
     * Retrieve the private key we should use to sign the assertion.
1161
     *
1162
     * @return XMLSecurityKey|null The key, or NULL if no key is specified.
1163
     */
1164
    public function getSignatureKey()
1165
    {
1166
        return $this->signatureKey;
1167
    }
1168
1169
    /**
1170
     * Set the private key we should use to sign the assertion.
1171
     *
1172
     * If the key is null, the assertion will be sent unsigned.
1173
     *
1174
     * @param XMLSecurityKey|null $signatureKey
1175
     */
1176
    public function setSignatureKey(XMLsecurityKey $signatureKey = null)
1177
    {
1178
        $this->signatureKey = $signatureKey;
1179
    }
1180
1181
    /**
1182
     * Return the key we should use to encrypt the assertion.
1183
     *
1184
     * @return XMLSecurityKey|null The key, or NULL if no key is specified..
1185
     *
1186
     */
1187
    public function getEncryptionKey()
1188
    {
1189
        return $this->encryptionKey;
1190
    }
1191
1192
    /**
1193
     * Set the private key we should use to encrypt the attributes.
1194
     *
1195
     * @param XMLSecurityKey|null $Key
1196
     */
1197
    public function setEncryptionKey(XMLSecurityKey $Key = null)
1198
    {
1199
        $this->encryptionKey = $Key;
1200
    }
1201
1202
    /**
1203
     * Set the certificates that should be included in the assertion.
1204
     *
1205
     * The certificates should be strings with the PEM encoded data.
1206
     *
1207
     * @param array $certificates An array of certificates.
1208
     */
1209
    public function setCertificates(array $certificates)
1210
    {
1211
        $this->certificates = $certificates;
1212
    }
1213
1214
    /**
1215
     * Retrieve the certificates that are included in the assertion.
1216
     *
1217
     * @return array An array of certificates.
1218
     */
1219
    public function getCertificates()
1220
    {
1221
        return $this->certificates;
1222
    }
1223
1224
    /**
1225
     * @return bool
1226
     */
1227
    public function getWasSignedAtConstruction()
1228
    {
1229
        return $this->wasSignedAtConstruction;
1230
    }
1231
1232
    /**
1233
     * Convert this assertion to an XML element.
1234
     *
1235
     * @param  \DOMNode|null $parentElement The DOM node the assertion should be created in.
1236
     * @return \DOMElement   This assertion.
1237
     */
1238
    public function toXML(\DOMNode $parentElement = null)
1239
    {
1240 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...
1241
            $document = DOMDocumentFactory::create();
1242
            $parentElement = $document;
1243
        } else {
1244
            $document = $parentElement->ownerDocument;
1245
        }
1246
1247
        $root = $document->createElementNS(Constants::NS_SAML, 'saml:' . 'Assertion');
1248
        $parentElement->appendChild($root);
1249
1250
        /* Ugly hack to add another namespace declaration to the root element. */
1251
        $root->setAttributeNS(Constants::NS_SAMLP, 'samlp:tmp', 'tmp');
1252
        $root->removeAttributeNS(Constants::NS_SAMLP, 'tmp');
1253
        $root->setAttributeNS(Constants::NS_XSI, 'xsi:tmp', 'tmp');
1254
        $root->removeAttributeNS(Constants::NS_XSI, 'tmp');
1255
        $root->setAttributeNS(Constants::NS_XS, 'xs:tmp', 'tmp');
1256
        $root->removeAttributeNS(Constants::NS_XS, 'tmp');
1257
1258
        $root->setAttribute('ID', $this->id);
1259
        $root->setAttribute('Version', '2.0');
1260
        $root->setAttribute('IssueInstant', gmdate('Y-m-d\TH:i:s\Z', $this->issueInstant));
1261
1262
        $issuer = Utils::addString($root, Constants::NS_SAML, 'saml:Issuer', $this->issuer);
1263
1264
        $this->addSubject($root);
1265
        $this->addConditions($root);
1266
        $this->addAuthnStatement($root);
1267
        if ($this->requiredEncAttributes == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1268
            $this->addAttributeStatement($root);
1269
        } else {
1270
            $this->addEncryptedAttributeStatement($root);
1271
        }
1272
1273
        if ($this->signatureKey !== null) {
1274
            Utils::insertSignature($this->signatureKey, $this->certificates, $root, $issuer->nextSibling);
1275
        }
1276
1277
        return $root;
1278
    }
1279
1280
    /**
1281
     * Add a Subject-node to the assertion.
1282
     *
1283
     * @param \DOMElement $root The assertion element we should add the subject to.
1284
     */
1285 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...
1286
    {
1287
        if ($this->nameId === null && $this->encryptedNameId === null) {
1288
            /* We don't have anything to create a Subject node for. */
1289
1290
            return;
1291
        }
1292
1293
        $subject = $root->ownerDocument->createElementNS(Constants::NS_SAML, 'saml:Subject');
1294
        $root->appendChild($subject);
1295
1296
        if ($this->encryptedNameId === null) {
1297
            Utils::addNameId($subject, $this->nameId);
0 ignored issues
show
Bug introduced by
It seems like $this->nameId can also be of type null; however, SAML2\Utils::addNameId() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1298
        } else {
1299
            $eid = $subject->ownerDocument->createElementNS(Constants::NS_SAML, 'saml:' . 'EncryptedID');
1300
            $subject->appendChild($eid);
1301
            $eid->appendChild($subject->ownerDocument->importNode($this->encryptedNameId, true));
1302
        }
1303
1304
        foreach ($this->SubjectConfirmation as $sc) {
1305
            $sc->toXML($subject);
1306
        }
1307
    }
1308
1309
1310
    /**
1311
     * Add a Conditions-node to the assertion.
1312
     *
1313
     * @param \DOMElement $root The assertion element we should add the conditions to.
1314
     */
1315
    private function addConditions(\DOMElement $root)
1316
    {
1317
        $document = $root->ownerDocument;
1318
1319
        $conditions = $document->createElementNS(Constants::NS_SAML, 'saml:Conditions');
1320
        $root->appendChild($conditions);
1321
1322
        if ($this->notBefore !== null) {
1323
            $conditions->setAttribute('NotBefore', gmdate('Y-m-d\TH:i:s\Z', $this->notBefore));
1324
        }
1325
        if ($this->notOnOrAfter !== null) {
1326
            $conditions->setAttribute('NotOnOrAfter', gmdate('Y-m-d\TH:i:s\Z', $this->notOnOrAfter));
1327
        }
1328
1329
        if ($this->validAudiences !== null) {
1330
            $ar = $document->createElementNS(Constants::NS_SAML, 'saml:AudienceRestriction');
1331
            $conditions->appendChild($ar);
1332
1333
            Utils::addStrings($ar, Constants::NS_SAML, 'saml:Audience', false, $this->validAudiences);
1334
        }
1335
    }
1336
1337
1338
    /**
1339
     * Add a AuthnStatement-node to the assertion.
1340
     *
1341
     * @param \DOMElement $root The assertion element we should add the authentication statement to.
1342
     */
1343
    private function addAuthnStatement(\DOMElement $root)
1344
    {
1345
        if ($this->authnInstant === null ||
1346
            (
1347
                $this->authnContextClassRef === null &&
1348
                $this->authnContextDecl === null &&
1349
                $this->authnContextDeclRef === null
1350
            )
1351
        ) {
1352
            /* No authentication context or AuthnInstant => no authentication statement. */
1353
1354
            return;
1355
        }
1356
1357
        $document = $root->ownerDocument;
1358
1359
        $authnStatementEl = $document->createElementNS(Constants::NS_SAML, 'saml:AuthnStatement');
1360
        $root->appendChild($authnStatementEl);
1361
1362
        $authnStatementEl->setAttribute('AuthnInstant', gmdate('Y-m-d\TH:i:s\Z', $this->authnInstant));
1363
1364
        if ($this->sessionNotOnOrAfter !== null) {
1365
            $authnStatementEl->setAttribute('SessionNotOnOrAfter', gmdate('Y-m-d\TH:i:s\Z', $this->sessionNotOnOrAfter));
1366
        }
1367
        if ($this->sessionIndex !== null) {
1368
            $authnStatementEl->setAttribute('SessionIndex', $this->sessionIndex);
1369
        }
1370
1371
        $authnContextEl = $document->createElementNS(Constants::NS_SAML, 'saml:AuthnContext');
1372
        $authnStatementEl->appendChild($authnContextEl);
1373
1374
        if (!empty($this->authnContextClassRef)) {
1375
            Utils::addString(
1376
                $authnContextEl,
1377
                Constants::NS_SAML,
1378
                'saml:AuthnContextClassRef',
1379
                $this->authnContextClassRef
1380
            );
1381
        }
1382
        if (!empty($this->authnContextDecl)) {
1383
            $this->authnContextDecl->toXML($authnContextEl);
1384
        }
1385
        if (!empty($this->authnContextDeclRef)) {
1386
            Utils::addString(
1387
                $authnContextEl,
1388
                Constants::NS_SAML,
1389
                'saml:AuthnContextDeclRef',
1390
                $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...
1391
            );
1392
        }
1393
1394
        Utils::addStrings(
1395
            $authnContextEl,
1396
            Constants::NS_SAML,
1397
            'saml:AuthenticatingAuthority',
1398
            false,
1399
            $this->AuthenticatingAuthority
1400
        );
1401
    }
1402
1403
1404
    /**
1405
     * Add an AttributeStatement-node to the assertion.
1406
     *
1407
     * @param \DOMElement $root The assertion element we should add the subject to.
1408
     */
1409
    private function addAttributeStatement(\DOMElement $root)
1410
    {
1411
        if (empty($this->attributes)) {
1412
            return;
1413
        }
1414
1415
        $document = $root->ownerDocument;
1416
1417
        $attributeStatement = $document->createElementNS(Constants::NS_SAML, 'saml:AttributeStatement');
1418
        $root->appendChild($attributeStatement);
1419
1420
        foreach ($this->attributes as $name => $values) {
1421
            $attribute = $document->createElementNS(Constants::NS_SAML, 'saml:Attribute');
1422
            $attributeStatement->appendChild($attribute);
1423
            $attribute->setAttribute('Name', $name);
1424
1425
            if ($this->nameFormat !== Constants::NAMEFORMAT_UNSPECIFIED) {
1426
                $attribute->setAttribute('NameFormat', $this->nameFormat);
1427
            }
1428
1429
            foreach ($values as $value) {
0 ignored issues
show
Bug introduced by
The expression $values of type object<DOMElement> is not traversable.
Loading history...
1430 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...
1431
                    $type = 'xs:string';
1432
                } elseif (is_int($value)) {
1433
                    $type = 'xs:integer';
1434
                } else {
1435
                    $type = null;
1436
                }
1437
1438
                $attributeValue = $document->createElementNS(Constants::NS_SAML, 'saml:AttributeValue');
1439
                $attribute->appendChild($attributeValue);
1440
                if ($type !== null) {
1441
                    $attributeValue->setAttributeNS(Constants::NS_XSI, 'xsi:type', $type);
1442
                }
1443
                if (is_null($value)) {
1444
                    $attributeValue->setAttributeNS(Constants::NS_XSI, 'xsi:nil', 'true');
1445
                }
1446
1447 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...
1448
                    for ($i = 0; $i < $value->length; $i++) {
1449
                        $node = $document->importNode($value->item($i), true);
1450
                        $attributeValue->appendChild($node);
1451
                    }
1452
                } else {
1453
                    $attributeValue->appendChild($document->createTextNode($value));
1454
                }
1455
            }
1456
        }
1457
    }
1458
1459
1460
    /**
1461
     * Add an EncryptedAttribute Statement-node to the assertion.
1462
     *
1463
     * @param \DOMElement $root The assertion element we should add the Encrypted Attribute Statement to.
1464
     */
1465
    private function addEncryptedAttributeStatement(\DOMElement $root)
1466
    {
1467
        if ($this->requiredEncAttributes == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1468
            return;
1469
        }
1470
1471
        $document = $root->ownerDocument;
1472
1473
        $attributeStatement = $document->createElementNS(Constants::NS_SAML, 'saml:AttributeStatement');
1474
        $root->appendChild($attributeStatement);
1475
1476
        foreach ($this->attributes as $name => $values) {
1477
            $document2 = DOMDocumentFactory::create();
1478
            $attribute = $document2->createElementNS(Constants::NS_SAML, 'saml:Attribute');
1479
            $attribute->setAttribute('Name', $name);
1480
            $document2->appendChild($attribute);
1481
1482
            if ($this->nameFormat !== Constants::NAMEFORMAT_UNSPECIFIED) {
1483
                $attribute->setAttribute('NameFormat', $this->nameFormat);
1484
            }
1485
1486
            foreach ($values as $value) {
0 ignored issues
show
Bug introduced by
The expression $values of type object<DOMElement> is not traversable.
Loading history...
1487 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...
1488
                    $type = 'xs:string';
1489
                } elseif (is_int($value)) {
1490
                    $type = 'xs:integer';
1491
                } else {
1492
                    $type = null;
1493
                }
1494
1495
                $attributeValue = $document2->createElementNS(Constants::NS_SAML, 'saml:AttributeValue');
1496
                $attribute->appendChild($attributeValue);
1497
                if ($type !== null) {
1498
                    $attributeValue->setAttributeNS(Constants::NS_XSI, 'xsi:type', $type);
1499
                }
1500
1501 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...
1502
                    for ($i = 0; $i < $value->length; $i++) {
1503
                        $node = $document2->importNode($value->item($i), true);
1504
                        $attributeValue->appendChild($node);
1505
                    }
1506
                } else {
1507
                    $attributeValue->appendChild($document2->createTextNode($value));
1508
                }
1509
            }
1510
            /*Once the attribute nodes are built, the are encrypted*/
1511
            $EncAssert = new XMLSecEnc();
1512
            $EncAssert->setNode($document2->documentElement);
1513
            $EncAssert->type = 'http://www.w3.org/2001/04/xmlenc#Element';
1514
            /*
1515
             * Attributes are encrypted with a session key and this one with
1516
             * $EncryptionKey
1517
             */
1518
            $symmetricKey = new XMLSecurityKey(XMLSecurityKey::AES256_CBC);
1519
            $symmetricKey->generateSessionKey();
1520
            $EncAssert->encryptKey($this->encryptionKey, $symmetricKey);
1521
            $EncrNode = $EncAssert->encryptNode($symmetricKey);
1522
1523
            $EncAttribute = $document->createElementNS(Constants::NS_SAML, 'saml:EncryptedAttribute');
1524
            $attributeStatement->appendChild($EncAttribute);
1525
            $n = $document->importNode($EncrNode, true);
1526
            $EncAttribute->appendChild($n);
1527
        }
1528
    }
1529
}
1530