Completed
Push — master ( b36a82...8a83fc )
by Jaime Pérez
03:30
created

Assertion::getAuthenticatingAuthority()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
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 array multi-dimensional array, indexed by attribute name with each value representing the attribute value
158
     *            of that attribute. This value is an array of \DOMNodeList|string|int
159
     */
160
    private $attributes;
161
162
    /**
163
     * The NameFormat used on all attributes.
164
     *
165
     * If more than one NameFormat is used, this will contain
166
     * the unspecified nameformat.
167
     *
168
     * @var string
169
     */
170
    private $nameFormat;
171
172
    /**
173
     * The private key we should use to sign the assertion.
174
     *
175
     * The private key can be null, in which case the assertion is sent unsigned.
176
     *
177
     * @var XMLSecurityKey|null
178
     */
179
    private $signatureKey;
180
181
    /**
182
     * List of certificates that should be included in the assertion.
183
     *
184
     * @var array
185
     */
186
    private $certificates;
187
188
    /**
189
     * The data needed to verify the signature.
190
     *
191
     * @var array|null
192
     */
193
    private $signatureData;
194
195
    /**
196
     * Boolean that indicates if attributes are encrypted in the
197
     * assertion or not.
198
     *
199
     * @var boolean
200
     */
201
    private $requiredEncAttributes;
202
203
    /**
204
     * The SubjectConfirmation elements of the Subject in the assertion.
205
     *
206
     * @var \SAML2\XML\saml\SubjectConfirmation[].
207
     */
208
    private $SubjectConfirmation;
209
210
    /**
211
     * @var bool
212
     */
213
    protected $wasSignedAtConstruction = false;
214
215
    /**
216
     * @var string|null
217
     */
218
    private $signatureMethod;
219
220
    /**
221
     * Constructor for SAML 2 assertions.
222
     *
223
     * @param \DOMElement|null $xml The input assertion.
224
     * @throws \Exception
225
     */
226
    public function __construct(\DOMElement $xml = null)
227
    {
228
        $this->id = Utils::getContainer()->generateId();
229
        $this->issueInstant = Temporal::getTime();
230
        $this->issuer = '';
231
        $this->authnInstant = Temporal::getTime();
232
        $this->attributes = array();
233
        $this->nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
234
        $this->certificates = array();
235
        $this->AuthenticatingAuthority = array();
236
        $this->SubjectConfirmation = array();
237
238
        if ($xml === null) {
239
            return;
240
        }
241
242
        if (!$xml->hasAttribute('ID')) {
243
            throw new \Exception('Missing ID attribute on SAML assertion.');
244
        }
245
        $this->id = $xml->getAttribute('ID');
246
247
        if ($xml->getAttribute('Version') !== '2.0') {
248
            /* Currently a very strict check. */
249
            throw new \Exception('Unsupported version: ' . $xml->getAttribute('Version'));
250
        }
251
252
        $this->issueInstant = Utils::xsDateTimeToTimestamp($xml->getAttribute('IssueInstant'));
253
254
        $issuer = Utils::xpQuery($xml, './saml_assertion:Issuer');
255
        if (empty($issuer)) {
256
            throw new \Exception('Missing <saml:Issuer> in assertion.');
257
        }
258
        $this->issuer = trim($issuer[0]->textContent);
259
260
        $this->parseSubject($xml);
261
        $this->parseConditions($xml);
262
        $this->parseAuthnStatement($xml);
263
        $this->parseAttributes($xml);
264
        $this->parseEncryptedAttributes($xml);
265
        $this->parseSignature($xml);
266
    }
267
268
    /**
269
     * Parse subject in assertion.
270
     *
271
     * @param \DOMElement $xml The assertion XML element.
272
     * @throws \Exception
273
     */
274
    private function parseSubject(\DOMElement $xml)
275
    {
276
        $subject = Utils::xpQuery($xml, './saml_assertion:Subject');
277
        if (empty($subject)) {
278
            /* No Subject node. */
279
280
            return;
281
        } elseif (count($subject) > 1) {
282
            throw new \Exception('More than one <saml:Subject> in <saml:Assertion>.');
283
        }
284
        $subject = $subject[0];
285
286
        $nameId = Utils::xpQuery(
287
            $subject,
288
            './saml_assertion:NameID | ./saml_assertion:EncryptedID/xenc:EncryptedData'
289
        );
290
        if (count($nameId) > 1) {
291
            throw new \Exception('More than one <saml:NameID> or <saml:EncryptedID> in <saml:Subject>.');
292
        } elseif (!empty($nameId)) {
293
            $nameId = $nameId[0];
294
            if ($nameId->localName === 'EncryptedData') {
295
                /* The NameID element is encrypted. */
296
                $this->encryptedNameId = $nameId;
297
            } else {
298
                $this->nameId = Utils::parseNameId($nameId);
299
            }
300
        }
301
302
        $subjectConfirmation = Utils::xpQuery($subject, './saml_assertion:SubjectConfirmation');
303
        if (empty($subjectConfirmation) && empty($nameId)) {
304
            throw new \Exception('Missing <saml:SubjectConfirmation> in <saml:Subject>.');
305
        }
306
307
        foreach ($subjectConfirmation as $sc) {
308
            $this->SubjectConfirmation[] = new SubjectConfirmation($sc);
309
        }
310
    }
311
312
    /**
313
     * Parse conditions in assertion.
314
     *
315
     * @param \DOMElement $xml The assertion XML element.
316
     * @throws \Exception
317
     */
318
    private function parseConditions(\DOMElement $xml)
319
    {
320
        $conditions = Utils::xpQuery($xml, './saml_assertion:Conditions');
321
        if (empty($conditions)) {
322
            /* No <saml:Conditions> node. */
323
324
            return;
325
        } elseif (count($conditions) > 1) {
326
            throw new \Exception('More than one <saml:Conditions> in <saml:Assertion>.');
327
        }
328
        $conditions = $conditions[0];
329
330 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...
331
            $notBefore = Utils::xsDateTimeToTimestamp($conditions->getAttribute('NotBefore'));
332
            if ($this->notBefore === null || $this->notBefore < $notBefore) {
333
                $this->notBefore = $notBefore;
334
            }
335
        }
336 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...
337
            $notOnOrAfter = Utils::xsDateTimeToTimestamp($conditions->getAttribute('NotOnOrAfter'));
338
            if ($this->notOnOrAfter === null || $this->notOnOrAfter > $notOnOrAfter) {
339
                $this->notOnOrAfter = $notOnOrAfter;
340
            }
341
        }
342
343
        for ($node = $conditions->firstChild; $node !== null; $node = $node->nextSibling) {
344
            if ($node instanceof \DOMText) {
345
                continue;
346
            }
347 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...
348
                throw new \Exception('Unknown namespace of condition: ' . var_export($node->namespaceURI, true));
349
            }
350
            switch ($node->localName) {
351
                case 'AudienceRestriction':
352
                    $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...
353
                    if ($this->validAudiences === null) {
354
                        /* The first (and probably last) AudienceRestriction element. */
355
                        $this->validAudiences = $audiences;
356
                    } else {
357
                        /*
358
                         * The set of AudienceRestriction are ANDed together, so we need
359
                         * the subset that are present in all of them.
360
                         */
361
                        $this->validAudiences = array_intersect($this->validAudiences, $audiences);
362
                    }
363
                    break;
364
                case 'OneTimeUse':
365
                    /* Currently ignored. */
366
                    break;
367
                case 'ProxyRestriction':
368
                    /* Currently ignored. */
369
                    break;
370
                default:
371
                    throw new \Exception('Unknown condition: ' . var_export($node->localName, true));
372
            }
373
        }
374
    }
375
376
    /**
377
     * Parse AuthnStatement in assertion.
378
     *
379
     * @param \DOMElement $xml The assertion XML element.
380
     * @throws \Exception
381
     */
382
    private function parseAuthnStatement(\DOMElement $xml)
383
    {
384
        $authnStatements = Utils::xpQuery($xml, './saml_assertion:AuthnStatement');
385
        if (empty($authnStatements)) {
386
            $this->authnInstant = null;
387
388
            return;
389
        } elseif (count($authnStatements) > 1) {
390
            throw new \Exception('More than one <saml:AuthnStatement> in <saml:Assertion> not supported.');
391
        }
392
        $authnStatement = $authnStatements[0];
393
394
        if (!$authnStatement->hasAttribute('AuthnInstant')) {
395
            throw new \Exception('Missing required AuthnInstant attribute on <saml:AuthnStatement>.');
396
        }
397
        $this->authnInstant = Utils::xsDateTimeToTimestamp($authnStatement->getAttribute('AuthnInstant'));
398
399
        if ($authnStatement->hasAttribute('SessionNotOnOrAfter')) {
400
            $this->sessionNotOnOrAfter = Utils::xsDateTimeToTimestamp($authnStatement->getAttribute('SessionNotOnOrAfter'));
401
        }
402
403
        if ($authnStatement->hasAttribute('SessionIndex')) {
404
            $this->sessionIndex = $authnStatement->getAttribute('SessionIndex');
405
        }
406
407
        $this->parseAuthnContext($authnStatement);
408
    }
409
410
    /**
411
     * Parse AuthnContext in AuthnStatement.
412
     *
413
     * @param \DOMElement $authnStatementEl
414
     * @throws \Exception
415
     */
416
    private function parseAuthnContext(\DOMElement $authnStatementEl)
417
    {
418
        // Get the AuthnContext element
419
        $authnContexts = Utils::xpQuery($authnStatementEl, './saml_assertion:AuthnContext');
420
        if (count($authnContexts) > 1) {
421
            throw new \Exception('More than one <saml:AuthnContext> in <saml:AuthnStatement>.');
422
        } elseif (empty($authnContexts)) {
423
            throw new \Exception('Missing required <saml:AuthnContext> in <saml:AuthnStatement>.');
424
        }
425
        $authnContextEl = $authnContexts[0];
426
427
        // Get the AuthnContextDeclRef (if available)
428
        $authnContextDeclRefs = Utils::xpQuery($authnContextEl, './saml_assertion:AuthnContextDeclRef');
429
        if (count($authnContextDeclRefs) > 1) {
430
            throw new \Exception(
431
                'More than one <saml:AuthnContextDeclRef> found?'
432
            );
433
        } elseif (count($authnContextDeclRefs) === 1) {
434
            $this->setAuthnContextDeclRef(trim($authnContextDeclRefs[0]->textContent));
435
        }
436
437
        // Get the AuthnContextDecl (if available)
438
        $authnContextDecls = Utils::xpQuery($authnContextEl, './saml_assertion:AuthnContextDecl');
439
        if (count($authnContextDecls) > 1) {
440
            throw new \Exception(
441
                'More than one <saml:AuthnContextDecl> found?'
442
            );
443
        } elseif (count($authnContextDecls) === 1) {
444
            $this->setAuthnContextDecl(new Chunk($authnContextDecls[0]));
445
        }
446
447
        // Get the AuthnContextClassRef (if available)
448
        $authnContextClassRefs = Utils::xpQuery($authnContextEl, './saml_assertion:AuthnContextClassRef');
449
        if (count($authnContextClassRefs) > 1) {
450
            throw new \Exception('More than one <saml:AuthnContextClassRef> in <saml:AuthnContext>.');
451
        } elseif (count($authnContextClassRefs) === 1) {
452
            $this->setAuthnContextClassRef(trim($authnContextClassRefs[0]->textContent));
453
        }
454
455
        // Constraint from XSD: MUST have one of the three
456
        if (empty($this->authnContextClassRef) && empty($this->authnContextDecl) && empty($this->authnContextDeclRef)) {
457
            throw new \Exception(
458
                'Missing either <saml:AuthnContextClassRef> or <saml:AuthnContextDeclRef> or <saml:AuthnContextDecl>'
459
            );
460
        }
461
462
        $this->AuthenticatingAuthority = Utils::extractStrings(
463
            $authnContextEl,
464
            Constants::NS_SAML,
465
            'AuthenticatingAuthority'
466
        );
467
    }
468
469
    /**
470
     * Parse attribute statements in assertion.
471
     *
472
     * @param \DOMElement $xml The XML element with the assertion.
473
     * @throws \Exception
474
     */
475
    private function parseAttributes(\DOMElement $xml)
476
    {
477
        $firstAttribute = true;
478
        $attributes = Utils::xpQuery($xml, './saml_assertion:AttributeStatement/saml_assertion:Attribute');
479
        foreach ($attributes as $attribute) {
480
            if (!$attribute->hasAttribute('Name')) {
481
                throw new \Exception('Missing name on <saml:Attribute> element.');
482
            }
483
            $name = $attribute->getAttribute('Name');
484
485
            if ($attribute->hasAttribute('NameFormat')) {
486
                $nameFormat = $attribute->getAttribute('NameFormat');
487
            } else {
488
                $nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
489
            }
490
491
            if ($firstAttribute) {
492
                $this->nameFormat = $nameFormat;
493
                $firstAttribute = false;
494
            } else {
495
                if ($this->nameFormat !== $nameFormat) {
496
                    $this->nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
497
                }
498
            }
499
500
            if (!array_key_exists($name, $this->attributes)) {
501
                $this->attributes[$name] = array();
502
            }
503
504
            $this->parseAttributeValue($attribute, $name);
505
        }
506
    }
507
508
    /**
509
     * @param \DOMNode $attribute
510
     * @param string   $attributeName
511
     */
512
    private function parseAttributeValue($attribute, $attributeName)
513
    {
514
        $values = Utils::xpQuery($attribute, './saml_assertion:AttributeValue');
515
        foreach ($values as $value) {
516
            $hasNonTextChildElements = false;
517
            foreach ($value->childNodes as $childNode) {
518
                /** @var \DOMNode $childNode */
519
                if ($childNode->nodeType !== XML_TEXT_NODE) {
520
                    $hasNonTextChildElements = true;
521
                    break;
522
                }
523
            }
524
525
            if ($hasNonTextChildElements) {
526
                $this->attributes[$attributeName][] = $value->childNodes;
527
                continue;
528
            }
529
530
            $type = $value->getAttribute('xsi:type');
531
            if ($type === 'xs:integer') {
532
                $this->attributes[$attributeName][] = (int)$value->textContent;
533
            } else {
534
                $this->attributes[$attributeName][] = trim($value->textContent);
535
            }
536
        }
537
    }
538
539
    /**
540
     * Parse encrypted attribute statements in assertion.
541
     *
542
     * @param \DOMElement $xml The XML element with the assertion.
543
     */
544
    private function parseEncryptedAttributes(\DOMElement $xml)
545
    {
546
        $this->encryptedAttributes = Utils::xpQuery(
547
            $xml,
548
            './saml_assertion:AttributeStatement/saml_assertion:EncryptedAttribute'
549
        );
550
    }
551
552
    /**
553
     * Parse signature on assertion.
554
     *
555
     * @param \DOMElement $xml The assertion XML element.
556
     */
557
    private function parseSignature(\DOMElement $xml)
558
    {
559
        /** @var null|\DOMAttr $signatureMethod */
560
        $signatureMethod = Utils::xpQuery($xml, './ds:Signature/ds:SignedInfo/ds:SignatureMethod/@Algorithm');
561
562
        /* Validate the signature element of the message. */
563
        $sig = Utils::validateElement($xml);
564
        if ($sig !== false) {
565
            $this->wasSignedAtConstruction = true;
566
            $this->certificates = $sig['Certificates'];
567
            $this->signatureData = $sig;
568
            $this->signatureMethod = $signatureMethod[0]->value;
569
        }
570
    }
571
572
    /**
573
     * Validate this assertion against a public key.
574
     *
575
     * If no signature was present on the assertion, we will return false.
576
     * Otherwise, true will be returned. An exception is thrown if the
577
     * signature validation fails.
578
     *
579
     * @param  XMLSecurityKey $key The key we should check against.
580
     * @return boolean        true if successful, false if it is unsigned.
581
     */
582
    public function validate(XMLSecurityKey $key)
583
    {
584
        assert('$key->type === \RobRichards\XMLSecLibs\XMLSecurityKey::RSA_SHA1');
585
586
        if ($this->signatureData === null) {
587
            return false;
588
        }
589
590
        Utils::validateSignature($this->signatureData, $key);
591
592
        return true;
593
    }
594
595
    /**
596
     * Retrieve the identifier of this assertion.
597
     *
598
     * @return string The identifier of this assertion.
599
     */
600
    public function getId()
601
    {
602
        return $this->id;
603
    }
604
605
    /**
606
     * Set the identifier of this assertion.
607
     *
608
     * @param string $id The new identifier of this assertion.
609
     */
610
    public function setId($id)
611
    {
612
        assert('is_string($id)');
613
614
        $this->id = $id;
615
    }
616
617
    /**
618
     * Retrieve the issue timestamp of this assertion.
619
     *
620
     * @return int The issue timestamp of this assertion, as an UNIX timestamp.
621
     */
622
    public function getIssueInstant()
623
    {
624
        return $this->issueInstant;
625
    }
626
627
    /**
628
     * Set the issue timestamp of this assertion.
629
     *
630
     * @param int $issueInstant The new issue timestamp of this assertion, as an UNIX timestamp.
631
     */
632
    public function setIssueInstant($issueInstant)
633
    {
634
        assert('is_int($issueInstant)');
635
636
        $this->issueInstant = $issueInstant;
637
    }
638
639
    /**
640
     * Retrieve the issuer if this assertion.
641
     *
642
     * @return string The issuer of this assertion.
643
     */
644
    public function getIssuer()
645
    {
646
        return $this->issuer;
647
    }
648
649
    /**
650
     * Set the issuer of this message.
651
     *
652
     * @param string $issuer The new issuer of this assertion.
653
     */
654
    public function setIssuer($issuer)
655
    {
656
        assert('is_string($issuer)');
657
658
        $this->issuer = $issuer;
659
    }
660
661
    /**
662
     * Retrieve the NameId of the subject in the assertion.
663
     *
664
     * The returned NameId is in the format used by \SAML2\Utils::addNameId().
665
     *
666
     * @see \SAML2\Utils::addNameId()
667
     * @return array|null The name identifier of the assertion.
668
     * @throws \Exception
669
     */
670
    public function getNameId()
671
    {
672
        if ($this->encryptedNameId !== null) {
673
            throw new \Exception('Attempted to retrieve encrypted NameID without decrypting it first.');
674
        }
675
676
        return $this->nameId;
677
    }
678
679
    /**
680
     * Set the NameId of the subject in the assertion.
681
     *
682
     * The NameId must be in the format accepted by \SAML2\Utils::addNameId().
683
     *
684
     * @see \SAML2\Utils::addNameId()
685
     * @param array|null $nameId The name identifier of the assertion.
686
     */
687
    public function setNameId($nameId)
688
    {
689
        assert('is_array($nameId) || is_null($nameId)');
690
691
        $this->nameId = $nameId;
692
    }
693
694
    /**
695
     * Check whether the NameId is encrypted.
696
     *
697
     * @return true if the NameId is encrypted, false if not.
698
     */
699
    public function isNameIdEncrypted()
700
    {
701
        return $this->encryptedNameId !== null;
702
    }
703
704
    /**
705
     * Encrypt the NameID in the Assertion.
706
     *
707
     * @param XMLSecurityKey $key The encryption key.
708
     */
709 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...
710
    {
711
        /* First create a XML representation of the NameID. */
712
        $doc = DOMDocumentFactory::create();
713
        $root = $doc->createElement('root');
714
        $doc->appendChild($root);
715
        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...
716
        $nameId = $root->firstChild;
717
718
        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...
719
720
        /* Encrypt the NameID. */
721
        $enc = new XMLSecEnc();
722
        $enc->setNode($nameId);
723
        // @codingStandardsIgnoreStart
724
        $enc->type = XMLSecEnc::Element;
725
        // @codingStandardsIgnoreEnd
726
727
        $symmetricKey = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
728
        $symmetricKey->generateSessionKey();
729
        $enc->encryptKey($key, $symmetricKey);
730
731
        $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...
732
        $this->nameId = null;
733
    }
734
735
    /**
736
     * Decrypt the NameId of the subject in the assertion.
737
     *
738
     * @param XMLSecurityKey $key       The decryption key.
739
     * @param array          $blacklist Blacklisted decryption algorithms.
740
     */
741 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...
742
    {
743
        if ($this->encryptedNameId === null) {
744
            /* No NameID to decrypt. */
745
746
            return;
747
        }
748
749
        $nameId = Utils::decryptElement($this->encryptedNameId, $key, $blacklist);
750
        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...
751
        $this->nameId = Utils::parseNameId($nameId);
752
753
        $this->encryptedNameId = null;
754
    }
755
756
    /**
757
     * Did this Assertion contain encrypted Attributes?
758
     *
759
     * @return bool
760
     */
761
    public function hasEncryptedAttributes()
762
    {
763
        return $this->encryptedAttributes !== null;
764
    }
765
766
    /**
767
     * Decrypt the assertion attributes.
768
     *
769
     * @param XMLSecurityKey $key
770
     * @param array $blacklist
771
     * @throws \Exception
772
     */
773
    public function decryptAttributes(XMLSecurityKey $key, array $blacklist = array())
774
    {
775
        if ($this->encryptedAttributes === null) {
776
            return;
777
        }
778
        $firstAttribute = true;
779
        $attributes = $this->encryptedAttributes;
780
        foreach ($attributes as $attributeEnc) {
781
            /*Decrypt node <EncryptedAttribute>*/
782
            $attribute = Utils::decryptElement(
783
                $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...
784
                $key,
785
                $blacklist
786
            );
787
788
            if (!$attribute->hasAttribute('Name')) {
789
                throw new \Exception('Missing name on <saml:Attribute> element.');
790
            }
791
            $name = $attribute->getAttribute('Name');
792
793
            if ($attribute->hasAttribute('NameFormat')) {
794
                $nameFormat = $attribute->getAttribute('NameFormat');
795
            } else {
796
                $nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
797
            }
798
799
            if ($firstAttribute) {
800
                $this->nameFormat = $nameFormat;
801
                $firstAttribute = false;
802
            } else {
803
                if ($this->nameFormat !== $nameFormat) {
804
                    $this->nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
805
                }
806
            }
807
808
            if (!array_key_exists($name, $this->attributes)) {
809
                $this->attributes[$name] = array();
810
            }
811
812
            $this->parseAttributeValue($attribute, $name);
813
        }
814
    }
815
816
    /**
817
     * Retrieve the earliest timestamp this assertion is valid.
818
     *
819
     * This function returns null if there are no restrictions on how early the
820
     * assertion can be used.
821
     *
822
     * @return int|null The earliest timestamp this assertion is valid.
823
     */
824
    public function getNotBefore()
825
    {
826
        return $this->notBefore;
827
    }
828
829
    /**
830
     * Set the earliest timestamp this assertion can be used.
831
     *
832
     * Set this to null if no limit is required.
833
     *
834
     * @param int|null $notBefore The earliest timestamp this assertion is valid.
835
     */
836
    public function setNotBefore($notBefore)
837
    {
838
        assert('is_int($notBefore) || is_null($notBefore)');
839
840
        $this->notBefore = $notBefore;
841
    }
842
843
    /**
844
     * Retrieve the expiration timestamp of this assertion.
845
     *
846
     * This function returns null if there are no restrictions on how
847
     * late the assertion can be used.
848
     *
849
     * @return int|null The latest timestamp this assertion is valid.
850
     */
851
    public function getNotOnOrAfter()
852
    {
853
        return $this->notOnOrAfter;
854
    }
855
856
    /**
857
     * Set the expiration timestamp of this assertion.
858
     *
859
     * Set this to null if no limit is required.
860
     *
861
     * @param int|null $notOnOrAfter The latest timestamp this assertion is valid.
862
     */
863
    public function setNotOnOrAfter($notOnOrAfter)
864
    {
865
        assert('is_int($notOnOrAfter) || is_null($notOnOrAfter)');
866
867
        $this->notOnOrAfter = $notOnOrAfter;
868
    }
869
870
    /**
871
     * Set $EncryptedAttributes if attributes will send encrypted
872
     *
873
     * @param boolean $ea true to encrypt attributes in the assertion.
874
     */
875
    public function setEncryptedAttributes($ea)
876
    {
877
        $this->requiredEncAttributes = $ea;
878
    }
879
880
    /**
881
     * Retrieve the audiences that are allowed to receive this assertion.
882
     *
883
     * This may be null, in which case all audiences are allowed.
884
     *
885
     * @return array|null The allowed audiences.
886
     */
887
    public function getValidAudiences()
888
    {
889
        return $this->validAudiences;
890
    }
891
892
    /**
893
     * Set the audiences that are allowed to receive this assertion.
894
     *
895
     * This may be null, in which case all audiences are allowed.
896
     *
897
     * @param array|null $validAudiences The allowed audiences.
898
     */
899
    public function setValidAudiences(array $validAudiences = null)
900
    {
901
        $this->validAudiences = $validAudiences;
902
    }
903
904
    /**
905
     * Retrieve the AuthnInstant of the assertion.
906
     *
907
     * @return int|null The timestamp the user was authenticated, or NULL if the user isn't authenticated.
908
     */
909
    public function getAuthnInstant()
910
    {
911
        return $this->authnInstant;
912
    }
913
914
915
    /**
916
     * Set the AuthnInstant of the assertion.
917
     *
918
     * @param int|null $authnInstant Timestamp the user was authenticated, or NULL if we don't want an AuthnStatement.
919
     */
920
    public function setAuthnInstant($authnInstant)
921
    {
922
        assert('is_int($authnInstant) || is_null($authnInstant)');
923
924
        $this->authnInstant = $authnInstant;
925
    }
926
927
    /**
928
     * Retrieve the session expiration timestamp.
929
     *
930
     * This function returns null if there are no restrictions on the
931
     * session lifetime.
932
     *
933
     * @return int|null The latest timestamp this session is valid.
934
     */
935
    public function getSessionNotOnOrAfter()
936
    {
937
        return $this->sessionNotOnOrAfter;
938
    }
939
940
    /**
941
     * Set the session expiration timestamp.
942
     *
943
     * Set this to null if no limit is required.
944
     *
945
     * @param int|null $sessionNotOnOrAfter The latest timestamp this session is valid.
946
     */
947
    public function setSessionNotOnOrAfter($sessionNotOnOrAfter)
948
    {
949
        assert('is_int($sessionNotOnOrAfter) || is_null($sessionNotOnOrAfter)');
950
951
        $this->sessionNotOnOrAfter = $sessionNotOnOrAfter;
952
    }
953
954
    /**
955
     * Retrieve the session index of the user at the IdP.
956
     *
957
     * @return string|null The session index of the user at the IdP.
958
     */
959
    public function getSessionIndex()
960
    {
961
        return $this->sessionIndex;
962
    }
963
964
    /**
965
     * Set the session index of the user at the IdP.
966
     *
967
     * Note that the authentication context must be set before the
968
     * session index can be inluded in the assertion.
969
     *
970
     * @param string|null $sessionIndex The session index of the user at the IdP.
971
     */
972
    public function setSessionIndex($sessionIndex)
973
    {
974
        assert('is_string($sessionIndex) || is_null($sessionIndex)');
975
976
        $this->sessionIndex = $sessionIndex;
977
    }
978
979
    /**
980
     * Retrieve the authentication method used to authenticate the user.
981
     *
982
     * This will return null if no authentication statement was
983
     * included in the assertion.
984
     *
985
     * Note that this returns either the AuthnContextClassRef or the AuthnConextDeclRef, whose definition overlaps
986
     * but is slightly different (consult the specification for more information).
987
     * This was done to work around an old bug of Shibboleth ( https://bugs.internet2.edu/jira/browse/SIDP-187 ).
988
     * Should no longer be required, please use either getAuthnConextClassRef or getAuthnContextDeclRef.
989
     *
990
     * @deprecated use getAuthnContextClassRef
991
     * @return string|null The authentication method.
992
     */
993
    public function getAuthnContext()
994
    {
995
        if (!empty($this->authnContextClassRef)) {
996
            return $this->authnContextClassRef;
997
        }
998
        if (!empty($this->authnContextDeclRef)) {
999
            return $this->authnContextDeclRef;
1000
        }
1001
        return null;
1002
    }
1003
1004
    /**
1005
     * Set the authentication method used to authenticate the user.
1006
     *
1007
     * If this is set to null, no authentication statement will be
1008
     * included in the assertion. The default is null.
1009
     *
1010
     * @deprecated use setAuthnContextClassRef
1011
     * @param string|null $authnContext The authentication method.
1012
     */
1013
    public function setAuthnContext($authnContext)
1014
    {
1015
        $this->setAuthnContextClassRef($authnContext);
1016
    }
1017
1018
    /**
1019
     * Retrieve the authentication method used to authenticate the user.
1020
     *
1021
     * This will return null if no authentication statement was
1022
     * included in the assertion.
1023
     *
1024
     * @return string|null The authentication method.
1025
     */
1026
    public function getAuthnContextClassRef()
1027
    {
1028
        return $this->authnContextClassRef;
1029
    }
1030
1031
    /**
1032
     * Set the authentication method used to authenticate the user.
1033
     *
1034
     * If this is set to null, no authentication statement will be
1035
     * included in the assertion. The default is null.
1036
     *
1037
     * @param string|null $authnContextClassRef The authentication method.
1038
     */
1039
    public function setAuthnContextClassRef($authnContextClassRef)
1040
    {
1041
        assert('is_string($authnContextClassRef) || is_null($authnContextClassRef)');
1042
1043
        $this->authnContextClassRef = $authnContextClassRef;
1044
    }
1045
1046
    /**
1047
     * Set the authentication context declaration.
1048
     *
1049
     * @param \SAML2\XML\Chunk $authnContextDecl
1050
     * @throws \Exception
1051
     */
1052
    public function setAuthnContextDecl(Chunk $authnContextDecl)
1053
    {
1054
        if (!empty($this->authnContextDeclRef)) {
1055
            throw new \Exception(
1056
                'AuthnContextDeclRef is already registered! May only have either a Decl or a DeclRef, not both!'
1057
            );
1058
        }
1059
1060
        $this->authnContextDecl = $authnContextDecl;
1061
    }
1062
1063
    /**
1064
     * Get the authentication context declaration.
1065
     *
1066
     * See:
1067
     * @url http://docs.oasis-open.org/security/saml/v2.0/saml-authn-context-2.0-os.pdf
1068
     *
1069
     * @return \SAML2\XML\Chunk|null
1070
     */
1071
    public function getAuthnContextDecl()
1072
    {
1073
        return $this->authnContextDecl;
1074
    }
1075
1076
    /**
1077
     * Set the authentication context declaration reference.
1078
     *
1079
     * @param string $authnContextDeclRef
1080
     * @throws \Exception
1081
     */
1082
    public function setAuthnContextDeclRef($authnContextDeclRef)
1083
    {
1084
        if (!empty($this->authnContextDecl)) {
1085
            throw new \Exception(
1086
                'AuthnContextDecl is already registered! May only have either a Decl or a DeclRef, not both!'
1087
            );
1088
        }
1089
1090
        $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...
1091
    }
1092
1093
    /**
1094
     * Get the authentication context declaration reference.
1095
     * URI reference that identifies an authentication context declaration.
1096
     *
1097
     * The URI reference MAY directly resolve into an XML document containing the referenced declaration.
1098
     *
1099
     * @return string
1100
     */
1101
    public function getAuthnContextDeclRef()
1102
    {
1103
        return $this->authnContextDeclRef;
1104
    }
1105
1106
    /**
1107
     * Retrieve the AuthenticatingAuthority.
1108
     *
1109
     *
1110
     * @return array
1111
     */
1112
    public function getAuthenticatingAuthority()
1113
    {
1114
        return $this->AuthenticatingAuthority;
1115
    }
1116
1117
    /**
1118
     * Set the AuthenticatingAuthority
1119
     *
1120
     *
1121
     * @param array.
1122
     */
1123
    public function setAuthenticatingAuthority($authenticatingAuthority)
1124
    {
1125
        $this->AuthenticatingAuthority = $authenticatingAuthority;
1126
    }
1127
1128
    /**
1129
     * Retrieve all attributes.
1130
     *
1131
     * @return array All attributes, as an associative array.
1132
     */
1133
    public function getAttributes()
1134
    {
1135
        return $this->attributes;
1136
    }
1137
1138
    /**
1139
     * Replace all attributes.
1140
     *
1141
     * @param array $attributes All new attributes, as an associative array.
1142
     */
1143
    public function setAttributes(array $attributes)
1144
    {
1145
        $this->attributes = $attributes;
1146
    }
1147
1148
    /**
1149
     * Retrieve the NameFormat used on all attributes.
1150
     *
1151
     * If more than one NameFormat is used in the received attributes, this
1152
     * returns the unspecified NameFormat.
1153
     *
1154
     * @return string The NameFormat used on all attributes.
1155
     */
1156
    public function getAttributeNameFormat()
1157
    {
1158
        return $this->nameFormat;
1159
    }
1160
1161
    /**
1162
     * Set the NameFormat used on all attributes.
1163
     *
1164
     * @param string $nameFormat The NameFormat used on all attributes.
1165
     */
1166
    public function setAttributeNameFormat($nameFormat)
1167
    {
1168
        assert('is_string($nameFormat)');
1169
1170
        $this->nameFormat = $nameFormat;
1171
    }
1172
1173
    /**
1174
     * Retrieve the SubjectConfirmation elements we have in our Subject element.
1175
     *
1176
     * @return array Array of \SAML2\XML\saml\SubjectConfirmation elements.
1177
     */
1178
    public function getSubjectConfirmation()
1179
    {
1180
        return $this->SubjectConfirmation;
1181
    }
1182
1183
    /**
1184
     * Set the SubjectConfirmation elements that should be included in the assertion.
1185
     *
1186
     * @param array $SubjectConfirmation Array of \SAML2\XML\saml\SubjectConfirmation elements.
1187
     */
1188
    public function setSubjectConfirmation(array $SubjectConfirmation)
1189
    {
1190
        $this->SubjectConfirmation = $SubjectConfirmation;
1191
    }
1192
1193
    /**
1194
     * Retrieve the private key we should use to sign the assertion.
1195
     *
1196
     * @return XMLSecurityKey|null The key, or NULL if no key is specified.
1197
     */
1198
    public function getSignatureKey()
1199
    {
1200
        return $this->signatureKey;
1201
    }
1202
1203
    /**
1204
     * Set the private key we should use to sign the assertion.
1205
     *
1206
     * If the key is null, the assertion will be sent unsigned.
1207
     *
1208
     * @param XMLSecurityKey|null $signatureKey
1209
     */
1210
    public function setSignatureKey(XMLsecurityKey $signatureKey = null)
1211
    {
1212
        $this->signatureKey = $signatureKey;
1213
    }
1214
1215
    /**
1216
     * Return the key we should use to encrypt the assertion.
1217
     *
1218
     * @return XMLSecurityKey|null The key, or NULL if no key is specified..
1219
     *
1220
     */
1221
    public function getEncryptionKey()
1222
    {
1223
        return $this->encryptionKey;
1224
    }
1225
1226
    /**
1227
     * Set the private key we should use to encrypt the attributes.
1228
     *
1229
     * @param XMLSecurityKey|null $Key
1230
     */
1231
    public function setEncryptionKey(XMLSecurityKey $Key = null)
1232
    {
1233
        $this->encryptionKey = $Key;
1234
    }
1235
1236
    /**
1237
     * Set the certificates that should be included in the assertion.
1238
     *
1239
     * The certificates should be strings with the PEM encoded data.
1240
     *
1241
     * @param array $certificates An array of certificates.
1242
     */
1243
    public function setCertificates(array $certificates)
1244
    {
1245
        $this->certificates = $certificates;
1246
    }
1247
1248
    /**
1249
     * Retrieve the certificates that are included in the assertion.
1250
     *
1251
     * @return array An array of certificates.
1252
     */
1253
    public function getCertificates()
1254
    {
1255
        return $this->certificates;
1256
    }
1257
1258
    /**
1259
     * @return bool
1260
     */
1261
    public function getWasSignedAtConstruction()
1262
    {
1263
        return $this->wasSignedAtConstruction;
1264
    }
1265
1266
    /**
1267
     * @return null|string
1268
     */
1269
    public function getSignatureMethod()
1270
    {
1271
        return $this->signatureMethod;
1272
    }
1273
1274
    /**
1275
     * Convert this assertion to an XML element.
1276
     *
1277
     * @param  \DOMNode|null $parentElement The DOM node the assertion should be created in.
1278
     * @return \DOMElement   This assertion.
1279
     */
1280
    public function toXML(\DOMNode $parentElement = null)
1281
    {
1282 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...
1283
            $document = DOMDocumentFactory::create();
1284
            $parentElement = $document;
1285
        } else {
1286
            $document = $parentElement->ownerDocument;
1287
        }
1288
1289
        $root = $document->createElementNS(Constants::NS_SAML, 'saml:' . 'Assertion');
1290
        $parentElement->appendChild($root);
1291
1292
        /* Ugly hack to add another namespace declaration to the root element. */
1293
        $root->setAttributeNS(Constants::NS_SAMLP, 'samlp:tmp', 'tmp');
1294
        $root->removeAttributeNS(Constants::NS_SAMLP, 'tmp');
1295
        $root->setAttributeNS(Constants::NS_XSI, 'xsi:tmp', 'tmp');
1296
        $root->removeAttributeNS(Constants::NS_XSI, 'tmp');
1297
        $root->setAttributeNS(Constants::NS_XS, 'xs:tmp', 'tmp');
1298
        $root->removeAttributeNS(Constants::NS_XS, 'tmp');
1299
1300
        $root->setAttribute('ID', $this->id);
1301
        $root->setAttribute('Version', '2.0');
1302
        $root->setAttribute('IssueInstant', gmdate('Y-m-d\TH:i:s\Z', $this->issueInstant));
1303
1304
        $issuer = Utils::addString($root, Constants::NS_SAML, 'saml:Issuer', $this->issuer);
1305
1306
        $this->addSubject($root);
1307
        $this->addConditions($root);
1308
        $this->addAuthnStatement($root);
1309
        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...
1310
            $this->addAttributeStatement($root);
1311
        } else {
1312
            $this->addEncryptedAttributeStatement($root);
1313
        }
1314
1315
        if ($this->signatureKey !== null) {
1316
            Utils::insertSignature($this->signatureKey, $this->certificates, $root, $issuer->nextSibling);
1317
        }
1318
1319
        return $root;
1320
    }
1321
1322
    /**
1323
     * Add a Subject-node to the assertion.
1324
     *
1325
     * @param \DOMElement $root The assertion element we should add the subject to.
1326
     */
1327 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...
1328
    {
1329
        if ($this->nameId === null && $this->encryptedNameId === null) {
1330
            /* We don't have anything to create a Subject node for. */
1331
1332
            return;
1333
        }
1334
1335
        $subject = $root->ownerDocument->createElementNS(Constants::NS_SAML, 'saml:Subject');
1336
        $root->appendChild($subject);
1337
1338
        if ($this->encryptedNameId === null) {
1339
            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...
1340
        } else {
1341
            $eid = $subject->ownerDocument->createElementNS(Constants::NS_SAML, 'saml:' . 'EncryptedID');
1342
            $subject->appendChild($eid);
1343
            $eid->appendChild($subject->ownerDocument->importNode($this->encryptedNameId, true));
1344
        }
1345
1346
        foreach ($this->SubjectConfirmation as $sc) {
1347
            $sc->toXML($subject);
1348
        }
1349
    }
1350
1351
1352
    /**
1353
     * Add a Conditions-node to the assertion.
1354
     *
1355
     * @param \DOMElement $root The assertion element we should add the conditions to.
1356
     */
1357
    private function addConditions(\DOMElement $root)
1358
    {
1359
        $document = $root->ownerDocument;
1360
1361
        $conditions = $document->createElementNS(Constants::NS_SAML, 'saml:Conditions');
1362
        $root->appendChild($conditions);
1363
1364
        if ($this->notBefore !== null) {
1365
            $conditions->setAttribute('NotBefore', gmdate('Y-m-d\TH:i:s\Z', $this->notBefore));
1366
        }
1367
        if ($this->notOnOrAfter !== null) {
1368
            $conditions->setAttribute('NotOnOrAfter', gmdate('Y-m-d\TH:i:s\Z', $this->notOnOrAfter));
1369
        }
1370
1371
        if ($this->validAudiences !== null) {
1372
            $ar = $document->createElementNS(Constants::NS_SAML, 'saml:AudienceRestriction');
1373
            $conditions->appendChild($ar);
1374
1375
            Utils::addStrings($ar, Constants::NS_SAML, 'saml:Audience', false, $this->validAudiences);
1376
        }
1377
    }
1378
1379
1380
    /**
1381
     * Add a AuthnStatement-node to the assertion.
1382
     *
1383
     * @param \DOMElement $root The assertion element we should add the authentication statement to.
1384
     */
1385
    private function addAuthnStatement(\DOMElement $root)
1386
    {
1387
        if ($this->authnInstant === null ||
1388
            (
1389
                $this->authnContextClassRef === null &&
1390
                $this->authnContextDecl === null &&
1391
                $this->authnContextDeclRef === null
1392
            )
1393
        ) {
1394
            /* No authentication context or AuthnInstant => no authentication statement. */
1395
1396
            return;
1397
        }
1398
1399
        $document = $root->ownerDocument;
1400
1401
        $authnStatementEl = $document->createElementNS(Constants::NS_SAML, 'saml:AuthnStatement');
1402
        $root->appendChild($authnStatementEl);
1403
1404
        $authnStatementEl->setAttribute('AuthnInstant', gmdate('Y-m-d\TH:i:s\Z', $this->authnInstant));
1405
1406
        if ($this->sessionNotOnOrAfter !== null) {
1407
            $authnStatementEl->setAttribute('SessionNotOnOrAfter', gmdate('Y-m-d\TH:i:s\Z', $this->sessionNotOnOrAfter));
1408
        }
1409
        if ($this->sessionIndex !== null) {
1410
            $authnStatementEl->setAttribute('SessionIndex', $this->sessionIndex);
1411
        }
1412
1413
        $authnContextEl = $document->createElementNS(Constants::NS_SAML, 'saml:AuthnContext');
1414
        $authnStatementEl->appendChild($authnContextEl);
1415
1416
        if (!empty($this->authnContextClassRef)) {
1417
            Utils::addString(
1418
                $authnContextEl,
1419
                Constants::NS_SAML,
1420
                'saml:AuthnContextClassRef',
1421
                $this->authnContextClassRef
1422
            );
1423
        }
1424
        if (!empty($this->authnContextDecl)) {
1425
            $this->authnContextDecl->toXML($authnContextEl);
1426
        }
1427
        if (!empty($this->authnContextDeclRef)) {
1428
            Utils::addString(
1429
                $authnContextEl,
1430
                Constants::NS_SAML,
1431
                'saml:AuthnContextDeclRef',
1432
                $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...
1433
            );
1434
        }
1435
1436
        Utils::addStrings(
1437
            $authnContextEl,
1438
            Constants::NS_SAML,
1439
            'saml:AuthenticatingAuthority',
1440
            false,
1441
            $this->AuthenticatingAuthority
1442
        );
1443
    }
1444
1445
1446
    /**
1447
     * Add an AttributeStatement-node to the assertion.
1448
     *
1449
     * @param \DOMElement $root The assertion element we should add the subject to.
1450
     */
1451
    private function addAttributeStatement(\DOMElement $root)
1452
    {
1453
        if (empty($this->attributes)) {
1454
            return;
1455
        }
1456
1457
        $document = $root->ownerDocument;
1458
1459
        $attributeStatement = $document->createElementNS(Constants::NS_SAML, 'saml:AttributeStatement');
1460
        $root->appendChild($attributeStatement);
1461
1462
        foreach ($this->attributes as $name => $values) {
1463
            $attribute = $document->createElementNS(Constants::NS_SAML, 'saml:Attribute');
1464
            $attributeStatement->appendChild($attribute);
1465
            $attribute->setAttribute('Name', $name);
1466
1467
            if ($this->nameFormat !== Constants::NAMEFORMAT_UNSPECIFIED) {
1468
                $attribute->setAttribute('NameFormat', $this->nameFormat);
1469
            }
1470
1471
            foreach ($values as $value) {
1472 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...
1473
                    $type = 'xs:string';
1474
                } elseif (is_int($value)) {
1475
                    $type = 'xs:integer';
1476
                } else {
1477
                    $type = null;
1478
                }
1479
1480
                $attributeValue = $document->createElementNS(Constants::NS_SAML, 'saml:AttributeValue');
1481
                $attribute->appendChild($attributeValue);
1482
                if ($type !== null) {
1483
                    $attributeValue->setAttributeNS(Constants::NS_XSI, 'xsi:type', $type);
1484
                }
1485
                if (is_null($value)) {
1486
                    $attributeValue->setAttributeNS(Constants::NS_XSI, 'xsi:nil', 'true');
1487
                }
1488
1489 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...
1490
                    for ($i = 0; $i < $value->length; $i++) {
1491
                        $node = $document->importNode($value->item($i), true);
1492
                        $attributeValue->appendChild($node);
1493
                    }
1494
                } else {
1495
                    $attributeValue->appendChild($document->createTextNode($value));
1496
                }
1497
            }
1498
        }
1499
    }
1500
1501
1502
    /**
1503
     * Add an EncryptedAttribute Statement-node to the assertion.
1504
     *
1505
     * @param \DOMElement $root The assertion element we should add the Encrypted Attribute Statement to.
1506
     */
1507
    private function addEncryptedAttributeStatement(\DOMElement $root)
1508
    {
1509
        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...
1510
            return;
1511
        }
1512
1513
        $document = $root->ownerDocument;
1514
1515
        $attributeStatement = $document->createElementNS(Constants::NS_SAML, 'saml:AttributeStatement');
1516
        $root->appendChild($attributeStatement);
1517
1518
        foreach ($this->attributes as $name => $values) {
1519
            $document2 = DOMDocumentFactory::create();
1520
            $attribute = $document2->createElementNS(Constants::NS_SAML, 'saml:Attribute');
1521
            $attribute->setAttribute('Name', $name);
1522
            $document2->appendChild($attribute);
1523
1524
            if ($this->nameFormat !== Constants::NAMEFORMAT_UNSPECIFIED) {
1525
                $attribute->setAttribute('NameFormat', $this->nameFormat);
1526
            }
1527
1528
            foreach ($values as $value) {
1529 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...
1530
                    $type = 'xs:string';
1531
                } elseif (is_int($value)) {
1532
                    $type = 'xs:integer';
1533
                } else {
1534
                    $type = null;
1535
                }
1536
1537
                $attributeValue = $document2->createElementNS(Constants::NS_SAML, 'saml:AttributeValue');
1538
                $attribute->appendChild($attributeValue);
1539
                if ($type !== null) {
1540
                    $attributeValue->setAttributeNS(Constants::NS_XSI, 'xsi:type', $type);
1541
                }
1542
1543 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...
1544
                    for ($i = 0; $i < $value->length; $i++) {
1545
                        $node = $document2->importNode($value->item($i), true);
1546
                        $attributeValue->appendChild($node);
1547
                    }
1548
                } else {
1549
                    $attributeValue->appendChild($document2->createTextNode($value));
1550
                }
1551
            }
1552
            /*Once the attribute nodes are built, the are encrypted*/
1553
            $EncAssert = new XMLSecEnc();
1554
            $EncAssert->setNode($document2->documentElement);
1555
            $EncAssert->type = 'http://www.w3.org/2001/04/xmlenc#Element';
1556
            /*
1557
             * Attributes are encrypted with a session key and this one with
1558
             * $EncryptionKey
1559
             */
1560
            $symmetricKey = new XMLSecurityKey(XMLSecurityKey::AES256_CBC);
1561
            $symmetricKey->generateSessionKey();
1562
            $EncAssert->encryptKey($this->encryptionKey, $symmetricKey);
1563
            $EncrNode = $EncAssert->encryptNode($symmetricKey);
1564
1565
            $EncAttribute = $document->createElementNS(Constants::NS_SAML, 'saml:EncryptedAttribute');
1566
            $attributeStatement->appendChild($EncAttribute);
1567
            $n = $document->importNode($EncrNode, true);
1568
            $EncAttribute->appendChild($n);
1569
        }
1570
    }
1571
}
1572