Completed
Push — master ( a99284...211219 )
by Jaime Pérez
06:06
created

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