Completed
Pull Request — master (#9)
by Asmir
163:43 queued 133:54
created

WsSecurityFilterRequest::createKeyInfo()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 35
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 35
c 0
b 0
f 0
rs 6.7272
cc 7
eloc 27
nc 5
nop 4
1
<?php
2
3
namespace GoetasWebservices\SoapServices\SoapClient\WssWsSecurity\Serializer;
4
5
use ass\XmlSecurity\DSig as XmlSecurityDSig;
6
use ass\XmlSecurity\Enc as XmlSecurityEnc;
7
use ass\XmlSecurity\Key as XmlSecurityKey;
8
use GoetasWebservices\SoapServices\SoapClient\WssWsSecurity\Security;
9
10
class WsSecurityFilterRequest extends AbstractWsSecurityFilter
11
{
12
    /**
13
     * Web Services Security: SOAP Message Security 1.0 (WS-Security 2004)
14
     */
15
    const NAME_WSS_SMS = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0';
16
17
    /**
18
     * Web Services Security UsernameToken Profile 1.0
19
     */
20
    const NAME_WSS_UTP = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0';
21
22
    /**
23
     * Web Services Security: SOAP Message Security 1.1 (WS-Security 2004)
24
     */
25
    const NAME_WSS_SMS_1_1 = 'http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1';
26
27
    /**
28
     * Web Services Security X.509 Certificate Token Profile
29
     */
30
    const NAME_WSS_X509 = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0';
31
32
    /**
33
     * The date format to be used with {@link \DateTime}
34
     */
35
    const DATETIME_FORMAT = 'Y-m-d\TH:i:s.000\Z';
36
37
    /**
38
     * (X509 3.2.1) Reference to a Subject Key Identifier
39
     */
40
    const TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER = 0;
41
42
    /**
43
     * (X509 3.2.1) Reference to a Security Token
44
     */
45
    const TOKEN_REFERENCE_SECURITY_TOKEN = 1;
46
47
    /**
48
     * (SMS_1.1 7.3) Key Identifiers
49
     */
50
    const TOKEN_REFERENCE_THUMBPRINT_SHA1 = 2;
51
52
    /**
53
     * (SMS 10) Add security timestamp.
54
     *
55
     * @var boolean
56
     */
57
    private $addTimestamp = true;
58
59
    /**
60
     * Encrypt the signature?
61
     *
62
     * @var boolean
63
     */
64
    private $encryptSignature = false;
65
66
    /**
67
     * (SMS 10) Security timestamp expires time in seconds.
68
     *
69
     * @var int
70
     */
71
    private $expires = 300;
72
73
    /**
74
     * Sign all headers.
75
     *
76
     * @var boolean
77
     */
78
    private $signAllHeaders = false;
79
80
    /**
81
     * @var \DateTime
82
     */
83
    private $initialTimestamp;
84
85
    /**
86
     * (X509 3.2) Token reference type for encryption.
87
     *
88
     * @var int
89
     */
90
    private $tokenReferenceEncryption = null;
91
92
    /**
93
     * (X509 3.2) Token reference type for signature.
94
     *
95
     * @var int
96
     */
97
    private $tokenReferenceSignature = null;
98
99
100
    public function setTimestampOptions($addTimestamp = true, $expires = 300)
101
    {
102
        $this->addTimestamp = $addTimestamp;
103
        $this->expires = $expires;
104
    }
105
106
    /**
107
     * @param \DateTime $initialTimestamp
108
     */
109
    public function __construct(\DateTime $initialTimestamp = null)
110
    {
111
        $this->initialTimestamp = $initialTimestamp;
112
    }
113
114
    /**
115
     * Set security options.
116
     *
117
     * @param int $tokenReference self::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER | self::TOKEN_REFERENCE_SECURITY_TOKEN | self::TOKEN_REFERENCE_THUMBPRINT_SHA1
118
     * @param boolean $encryptSignature Encrypt signature
119
     *
120
     * @return void
121
     */
122
    public function setSecurityOptionsEncryption($tokenReference, $encryptSignature = false)
123
    {
124
        $this->tokenReferenceEncryption = $tokenReference;
125
        $this->encryptSignature = $encryptSignature;
126
    }
127
128
    /**
129
     * Set security options.
130
     *
131
     * @param int $tokenReference self::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER | self::TOKEN_REFERENCE_SECURITY_TOKEN | self::TOKEN_REFERENCE_THUMBPRINT_SHA1
132
     * @param boolean $signAllHeaders Sign all headers?
133
     *
134
     * @return void
135
     */
136
    public function setSecurityOptionsSignature($tokenReference, $signAllHeaders = false)
137
    {
138
        $this->tokenReferenceSignature = $tokenReference;
139
        $this->signAllHeaders = $signAllHeaders;
140
    }
141
142
    /**
143
     * Adds the configured KeyInfo to the parentNode.
144
     *
145
     * @param \DOMDocument $dom
146
     * @param int $tokenReference Token reference type
147
     * @param string $guid Unique ID
148
     * @param XmlSecurityKey $xmlSecurityKey XML security key
149
     *
150
     * @return \DOMElement
151
     */
152
    private function createKeyInfo(\DOMDocument $dom, $tokenReference, $guid, XmlSecurityKey $xmlSecurityKey = null)
153
    {
154
        $keyInfo = $dom->createElementNS(XmlSecurityDSig::NS_XMLDSIG, 'KeyInfo');
155
        $securityTokenReference = $dom->createElementNS(self::NS_WSS, 'SecurityTokenReference');
156
        $keyInfo->appendChild($securityTokenReference);
157
        // security token
158
        if (self::TOKEN_REFERENCE_SECURITY_TOKEN === $tokenReference) {
159
            $reference = $dom->createElementNS(self::NS_WSS, 'Reference');
160
            $reference->setAttribute('URI', '#' . $guid);
161
            if (null !== $xmlSecurityKey) {
162
                $reference->setAttribute('ValueType', self::NAME_WSS_X509 . '#X509v3');
163
            }
164
            $securityTokenReference->appendChild($reference);
165
            // subject key identifier
166
        } elseif (self::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER === $tokenReference && null !== $xmlSecurityKey) {
167
            $keyIdentifier = $dom->createElementNS(self::NS_WSS, 'KeyIdentifier');
168
            $keyIdentifier->setAttribute('EncodingType', self::NAME_WSS_SMS . '#Base64Binary');
169
            $keyIdentifier->setAttribute('ValueType', self::NAME_WSS_X509 . '#509SubjectKeyIdentifier');
170
            $securityTokenReference->appendChild($keyIdentifier);
171
            $certificate = $xmlSecurityKey->getX509SubjectKeyIdentifier();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ass\XmlSecurity\Key as the method getX509SubjectKeyIdentifier() does only exist in the following sub-classes of ass\XmlSecurity\Key: ass\XmlSecurity\Key\PrivatePublic, ass\XmlSecurity\Key\Rsa15, ass\XmlSecurity\Key\RsaOaepMgf1p, ass\XmlSecurity\Key\RsaSha1, ass\XmlSecurity\Key\RsaSha256, ass\XmlSecurity\Key\RsaSha384, ass\XmlSecurity\Key\RsaSha512. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
172
            $dataNode = new \DOMText($certificate);
173
            $keyIdentifier->appendChild($dataNode);
174
            // thumbprint sha1
175
        } elseif (self::TOKEN_REFERENCE_THUMBPRINT_SHA1 === $tokenReference && null !== $xmlSecurityKey) {
176
            $keyIdentifier = $dom->createElementNS(self::NS_WSS, 'KeyIdentifier');
177
            $keyIdentifier->setAttribute('EncodingType', self::NAME_WSS_SMS . '#Base64Binary');
178
            $keyIdentifier->setAttribute('ValueType', self::NAME_WSS_SMS_1_1 . '#ThumbprintSHA1');
179
            $securityTokenReference->appendChild($keyIdentifier);
180
            $thumbprintSha1 = base64_encode(sha1(base64_decode($xmlSecurityKey->getX509Certificate(true)), true));
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ass\XmlSecurity\Key as the method getX509Certificate() does only exist in the following sub-classes of ass\XmlSecurity\Key: ass\XmlSecurity\Key\PrivatePublic, ass\XmlSecurity\Key\Rsa15, ass\XmlSecurity\Key\RsaOaepMgf1p, ass\XmlSecurity\Key\RsaSha1, ass\XmlSecurity\Key\RsaSha256, ass\XmlSecurity\Key\RsaSha384, ass\XmlSecurity\Key\RsaSha512. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
181
            $dataNode = new \DOMText($thumbprintSha1);
182
            $keyIdentifier->appendChild($dataNode);
183
        }
184
185
        return $keyInfo;
186
    }
187
188
    /**
189
     * Create a list of \DOMNodes that should be encrypted.
190
     *
191
     * @param \DOMDocument $dom DOMDocument to query
192
     *
193
     * @return \DOMNodeList
194
     */
195
    private function createNodeListForEncryption(\DOMDocument $dom)
196
    {
197
        $xpath = new \DOMXPath($dom);
198
        $xpath->registerNamespace('SOAP-ENV', $dom->documentElement->namespaceURI);
199
        $xpath->registerNamespace('ds', XmlSecurityDSig::NS_XMLDSIG);
200
        if ($this->encryptSignature === true) {
201
            $query = '//ds:Signature | //SOAP-ENV:Body';
202
        } else {
203
            $query = '//SOAP-ENV:Body';
204
        }
205
206
        return $xpath->query($query);
207
    }
208
209
    /**
210
     * Create a list of \DOMNodes that should be signed.
211
     *
212
     * @param \DOMDocument $dom DOMDocument to query
213
     * @param \DOMElement $security Security element
214
     *
215
     * @return array(\DOMNode)
0 ignored issues
show
Documentation introduced by
The doc-type array(\DOMNode) could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
216
     */
217
    private function createNodeListForSigning(\DOMDocument $dom, \DOMElement $security)
218
    {
219
        $nodes = array();
220
        $body = $dom->getElementsByTagNameNS($dom->documentElement->namespaceURI, 'Body')->item(0);
221
        if (null !== $body) {
222
            $nodes[] = $body;
223
        }
224
        foreach ($security->childNodes as $node) {
225
            if (XML_ELEMENT_NODE === $node->nodeType) {
226
                $nodes[] = $node;
227
            }
228
        }
229
        if ($this->signAllHeaders) {
230
            foreach ($security->parentNode->childNodes as $node) {
231
                if (XML_ELEMENT_NODE === $node->nodeType &&
232
                    self::NS_WSS !== $node->namespaceURI
233
                ) {
234
                    $nodes[] = $node;
235
                }
236
            }
237
        }
238
        return $nodes;
239
    }
240
241
242
    /**
243
     * Modify the given request XML.
244
     *
245
     * @param \DOMElement $currentNode,
0 ignored issues
show
Documentation introduced by
There is no parameter named $currentNode,. Did you maybe mean $currentNode?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
246
     * @param Security $securityData
247
     *
248
     * @return \DOMElement
249
     */
250
    public function filterDom(\DOMElement $currentNode, Security $securityData)
251
    {
252
        $dom = $currentNode->ownerDocument;
253
        $root = $dom->documentElement;
254
255
        $namespaces = array(
256
            'ws' => self::NS_WSS,
257
            'wsu' => self::NS_WSU,
258
            XmlSecurityDSig::PFX_XMLDSIG => XmlSecurityDSig::NS_XMLDSIG,
259
            XmlSecurityEnc::PFX_XMLENC => XmlSecurityEnc::NS_XMLENC,
260
        );
261
262
        foreach ($namespaces as $prefix => $ns) {
263
            $root->setAttributeNS(
264
                'http://www.w3.org/2000/xmlns/', // xmlns namespace URI
265
                'xmlns:'.$prefix,
266
                $ns
267
            );
268
        }
269
270
        $security = $dom->createElementNS(self::NS_WSS, $root->lookupPrefix(self::NS_WSS).':Security');
271
        $currentNode->parentNode->replaceChild($security, $currentNode);
272
273
        // init timestamp
274
        $dt = $this->initialTimestamp ?: new \DateTime('now', new \DateTimeZone('UTC'));
275
276
        if (true === $this->addTimestamp || null !== $this->expires) {
277
            $this->handleTimestamp($security, $dt);
278
        }
279
280
        if (null !== $securityData->getUsername()) {
281
            $this->handleUsername($security, $dt, $securityData);
282
        }
283
284
        if (null !== $this->userSecurityKey && $this->userSecurityKey->hasKeys()) {
285
            $signature = $this->handleSignature($security);
286
287
            // encrypt soap document
288
            if (null !== $this->serviceSecurityKey && $this->serviceSecurityKey->hasKeys()) {
289
                $this->handleEncryption($security, $signature);
290
            }
291
        }
292
        return $security;
293
    }
294
295
    /**
296
     * Generate a pseudo-random version 4 UUID.
297
     *
298
     * @see http://de.php.net/manual/en/function.uniqid.php#94959
299
     *
300
     * @return string
301
     */
302
    private static function generateUUID()
303
    {
304
        return sprintf(
305
            '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
306
            // 32 bits for "time_low"
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
307
            mt_rand(0, 0xffff), mt_rand(0, 0xffff),
308
            // 16 bits for "time_mid"
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
309
            mt_rand(0, 0xffff),
310
            // 16 bits for "time_hi_and_version",
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
311
            // four most significant bits holds version number 4
312
            mt_rand(0, 0x0fff) | 0x4000,
313
            // 16 bits, 8 bits for "clk_seq_hi_res",
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
314
            // 8 bits for "clk_seq_low",
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
315
            // two most significant bits holds zero and one for variant DCE1.1
316
            mt_rand(0, 0x3fff) | 0x8000,
317
            // 48 bits for "node"
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
318
            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
319
        );
320
    }
321
322
    /**
323
     * @param \DOMElement $security
324
     * @param \DateTime $dt
325
     */
326
    private function handleTimestamp(\DOMElement $security, \DateTime $dt)
327
    {
328
        $dom = $security->ownerDocument;
329
        $timestamp = $dom->createElementNS(self::NS_WSU, 'Timestamp');
330
        $created = $dom->createElementNS(self::NS_WSU, 'Created', $dt->format(self::DATETIME_FORMAT));
331
        $timestamp->appendChild($created);
332
        if (null !== $this->expires) {
333
            $dt = clone $dt;
334
            $dt->modify('+' . $this->expires . ' seconds');
335
            $expiresTimestamp = $dt->format(self::DATETIME_FORMAT);
336
            $expires = $dom->createElementNS(self::NS_WSU, 'Expires', $expiresTimestamp);
337
            $timestamp->appendChild($expires);
338
        }
339
        $security->appendChild($timestamp);
340
    }
341
342
    /**
343
     * @param \DOMElement $security
344
     * @param $dt
345
     * @param Security $securityData
346
     */
347
    private function handleUsername(\DOMElement $security, $dt, Security $securityData)
348
    {
349
        $dom = $security->ownerDocument;
350
351
        $usernameToken = $dom->createElementNS(self::NS_WSS, 'UsernameToken');
352
        $security->appendChild($usernameToken);
353
354
        $username = $dom->createElementNS(self::NS_WSS, 'Username', $securityData->getUsername());
355
        $usernameToken->appendChild($username);
356
357
        if (null !== $securityData->getPassword()
358
            && (null === $this->userSecurityKey
359
                || (null !== $this->userSecurityKey && !$this->userSecurityKey->hasPrivateKey()))
360
        ) {
361
362
            if ($securityData->isPasswordDigest()) {
363
                $nonce = mt_rand();
364
                $password = base64_encode(sha1($nonce . $dt->format(self::DATETIME_FORMAT) . $securityData->getPassword(), true));
365
                $passwordType = self::NAME_WSS_UTP . '#PasswordDigest';
366
            } else {
367
                $password = $securityData->getPassword();
368
                $passwordType = self::NAME_WSS_UTP . '#PasswordText';
369
            }
370
371
            $password = $dom->createElementNS(self::NS_WSS, 'Password', $password);
372
            $password->setAttribute('Type', $passwordType);
373
            $usernameToken->appendChild($password);
374
            if ($securityData->isPasswordDigest()) {
375
                $nonce = $dom->createElementNS(self::NS_WSS, 'Nonce', base64_encode($nonce));
0 ignored issues
show
Bug introduced by
The variable $nonce does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
376
                $usernameToken->appendChild($nonce);
377
378
                $created = $dom->createElementNS(self::NS_WSU, 'Created', $dt->format(self::DATETIME_FORMAT));
379
                $usernameToken->appendChild($created);
380
            }
381
        }
382
    }
383
384
    /**
385
     * @param \DOMElement $security
386
     * @return \DOMElement
387
     */
388
    private function handleSignature(\DOMElement $security)
389
    {
390
        $dom = $security->ownerDocument;
391
392
        // this is fundamental for the signature
393
        // formatting the dom adds nodes that are not signed
394
        $dom->formatOutput = false;
395
        $dom->preserveWhiteSpace = true;
396
397
        $guid = 'CertId-' . self::generateUUID();
398
        // add token references
399
        $keyInfo = null;
400
        if (null !== $this->tokenReferenceSignature) {
401
            $keyInfo = $this->createKeyInfo($dom, $this->tokenReferenceSignature, $guid, $this->userSecurityKey->getPublicKey());
402
        }
403
        $nodes = $this->createNodeListForSigning($dom, $security);
404
405
406
        $signature = XmlSecurityDSig::createSignature($this->userSecurityKey->getPrivateKey(), XmlSecurityDSig::EXC_C14N, $security, null, $keyInfo);
407
408
        if ((!$prefix = $security->lookupPrefix(self::NS_WSU)) && (!$prefix = $security->ownerDocument->lookupPrefix(self::NS_WSU))) {
409
            $prefix = 'ns-'.  substr(sha1(self::NS_WSU), 0, 8);
410
        }
411
412
        $options = array(
413
            'id_ns_prefix' => $prefix,
414
            'id_prefix_ns' => self::NS_WSU,
415
        );
416
        foreach ($nodes as $node) {
417
            XmlSecurityDSig::addNodeToSignature($signature, $node, XmlSecurityDSig::SHA1, XmlSecurityDSig::EXC_C14N, $options);
418
        }
419
        XmlSecurityDSig::signDocument($signature, $this->userSecurityKey->getPrivateKey(), XmlSecurityDSig::EXC_C14N);
420
421
        $publicCertificate = $this->userSecurityKey->getPublicKey()->getX509Certificate(true);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ass\XmlSecurity\Key as the method getX509Certificate() does only exist in the following sub-classes of ass\XmlSecurity\Key: ass\XmlSecurity\Key\PrivatePublic, ass\XmlSecurity\Key\Rsa15, ass\XmlSecurity\Key\RsaOaepMgf1p, ass\XmlSecurity\Key\RsaSha1, ass\XmlSecurity\Key\RsaSha256, ass\XmlSecurity\Key\RsaSha384, ass\XmlSecurity\Key\RsaSha512. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
422
        $binarySecurityToken = $dom->createElementNS(self::NS_WSS, 'BinarySecurityToken', $publicCertificate);
423
        $binarySecurityToken->setAttribute('EncodingType', self::NAME_WSS_SMS . '#Base64Binary');
424
        $binarySecurityToken->setAttribute('ValueType', self::NAME_WSS_X509 . '#X509v3');
425
426
        $security->insertBefore($binarySecurityToken, $signature);
427
428
        $binarySecurityToken->setAttributeNs(self::NS_WSU, $prefix.':Id', $guid);
429
430
        return $signature;
431
    }
432
433
    /**
434
     * @param \DOMElement $security
435
     * @param \DOMElement $signature
436
     */
437
    private function handleEncryption(\DOMElement $security, \DOMElement $signature)
438
    {
439
        $dom = $security->ownerDocument;
440
        $guid = 'EncKey-' . self::generateUUID();
441
        // add token references
442
        $keyInfo = null;
443
        if (null !== $this->tokenReferenceEncryption) {
444
            $keyInfo = $this->createKeyInfo($dom, $this->tokenReferenceEncryption, $guid, $this->serviceSecurityKey->getPublicKey());
445
        }
446
        $encryptedKey = XmlSecurityEnc::createEncryptedKey($guid, $this->serviceSecurityKey->getPrivateKey(), $this->serviceSecurityKey->getPublicKey(), $security, $signature, $keyInfo);
447
        $referenceList = XmlSecurityEnc::createReferenceList($encryptedKey);
448
        // token reference to encrypted key
449
        $keyInfo = $this->createKeyInfo($dom, self::TOKEN_REFERENCE_SECURITY_TOKEN, $guid);
450
        $nodes = $this->createNodeListForEncryption($dom);
451
        foreach ($nodes as $node) {
452
            $type = XmlSecurityEnc::ELEMENT;
453
            if ($node->localName == 'Body') {
454
                $type = XmlSecurityEnc::CONTENT;
455
            }
456
            XmlSecurityEnc::encryptNode($node, $type, $this->serviceSecurityKey->getPrivateKey(), $referenceList, $keyInfo);
457
        }
458
    }
459
}
460