Completed
Push — master ( a7e5b2...5514ed )
by Thijs
03:16
created

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