Completed
Push — master ( c5dc47...06408e )
by Dieter
07:50
created

SoapHeader4::createSoapHeaders()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 129

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 104
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 129
ccs 104
cts 104
cp 1
rs 7.3777
c 0
b 0
f 0
cc 6
nc 8
nop 4
crap 6

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * amadeus-ws-client
4
 *
5
 * Copyright 2015 Amadeus Benelux NV
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 * http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 *
19
 * @package Amadeus
20
 * @license https://opensource.org/licenses/Apache-2.0 Apache 2.0
21
 */
22
23
namespace Amadeus\Client\Session\Handler;
24
25
use Amadeus\Client;
26
27
/**
28
 * SoapHeader4: Session Handler for web service applications using Amadeus WS Soap Header v4.
29
 *
30
 * @package Amadeus\Client\Session\Handler
31
 * @author Dieter Devlieghere <[email protected]>
32
 */
33
class SoapHeader4 extends Base
34
{
35
    /**
36
     * XPATH query to retrieve the SOAPAction from the WSDL for a given message.
37
     *
38
     * @var string
39
     */
40
    const XPATH_OPERATION_ACTION = 'string(//wsdl:operation[./@name="%s"]/soap:operation/@soapAction)';
41
    /**
42
     * XPATH query to retrieve the server endpoint from the WSDL.
43
     *
44
     * @var string
45
     */
46
    const XPATH_ENDPOINT = 'string(/wsdl:definitions/wsdl:service/wsdl:port/soap:address/@location)';
47
48
    /**
49
     * SoapHeader - Session
50
     * TransactionStatusCode for starting new sessions.
51
     */
52
    const TRANSACTION_STATUS_CODE_START = 'Start';
53
54
    /**
55
     * SoapHeader - Session
56
     * TransactionStatusCode for active stateful sessions.
57
     */
58
    const TRANSACTION_STATUS_CODE_INSERIES = 'InSeries';
59
60
    /**
61
     * SoapHeader - Session
62
     * TransactionStatusCode for ending sessions.
63
     */
64
    const TRANSACTION_STATUS_CODE_END = 'End';
65
66
    /**
67
     * Switch between stateful & stateless sessions. Default: stateful
68
     *
69
     * @var bool
70
     */
71
    protected $isStateful = true;
72
73
    protected $enableTransactionFlowLink = false;
74
75
    /**
76
     * TransactionFlowLink Consumer ID
77
     *
78
     * @var string|null
79
     */
80
    protected $consumerId;
81
82
    /**
83
     * @param bool $stateful
84
     */
85 192
    public function setStateful($stateful)
86
    {
87 192
        $this->isStateful = $stateful;
88 192
    }
89
90
    /**
91
     * Check whether we are running in stateful mode (true) or in stateless mode (false)
92
     *
93
     * @return bool
94
     */
95 84
    public function isStateful()
96
    {
97 84
        return $this->isStateful;
98
    }
99
100
    /**
101
     * Is the TransactionFlowLink header enabled?
102
     *
103
     * @return bool
104
     */
105 60
    public function isTransactionFlowLinkEnabled()
106
    {
107 60
        return $this->enableTransactionFlowLink;
108
    }
109
110
    /**
111
     * Enable or disable TransactionFlowLink header
112
     *
113
     * @param bool $enabled
114
     */
115 192
    public function setTransactionFlowLink($enabled)
116
    {
117 192
        $this->enableTransactionFlowLink = $enabled;
118 192
    }
119
120
    /**
121
     * Get the TransactionFlowLink Consumer ID
122
     *
123
     * @param bool $generate Whether to generate a consumer ID
124
     * @return string|null
125
     */
126 12
    public function getConsumerId($generate = false)
127
    {
128 12
        if (is_null($this->consumerId) && $generate) {
129 4
            $this->consumerId = $this->generateGuid();
130 2
        }
131
132 12
        return $this->consumerId;
133
    }
134
135
    /**
136
     * Set the TransactionFlowLink Consumer ID
137
     *
138
     * @param string $id
139
     * @return void
140
     */
141 192
    public function setConsumerId($id)
142
    {
143 192
        $this->consumerId = $id;
144 192
    }
145
146
    /**
147
     * Handles authentication & sessions
148
     *
149
     * If authenticated, increment sequence number for next message and set session info to soapheader
150
     * If not, set auth info to soapheader
151
     *
152
     * @uses $this->isAuthenticated
153
     * @uses $this->sessionData
154
     * @param string $messageName
155
     * @param array $messageOptions
156
     */
157 32
    protected function prepareForNextMessage($messageName, $messageOptions)
158
    {
159 32
        if ($this->isAuthenticated === true && is_int($this->sessionData['sequenceNumber'])) {
160 4
            $this->sessionData['sequenceNumber']++;
161 2
        }
162
163 32
        $headers = $this->createSoapHeaders($this->sessionData, $this->params, $messageName, $messageOptions);
164
165 32
        $this->getSoapClient($messageName)->__setSoapHeaders(null);
166 32
        $this->getSoapClient($messageName)->__setSoapHeaders($headers);
167 32
    }
168
169
170
    /**
171
     * Handles post message actions
172
     *
173
     * - look for session info and set status variables
174
     * - checks for message errors?
175
     * - ends terminated sessions
176
     *
177
     * @param string $messageName
178
     * @param string $lastResponse
179
     * @param array $messageOptions
180
     * @param mixed $result
181
     * @return void
182
     */
183 32
    protected function handlePostMessage($messageName, $lastResponse, $messageOptions, $result)
184
    {
185
        //CHECK FOR SESSION DATA:
186 32
        if ($this->isStateful() === true) {
187
            //We need to extract session info
188 12
            $this->sessionData = $this->getSessionDataFromHeader($lastResponse);
189 12
            $this->isAuthenticated = (!empty($this->sessionData['sessionId']) &&
190 12
                !empty($this->sessionData['sequenceNumber']) &&
191 10
                !empty($this->sessionData['securityToken']));
192 6
        } else {
193 20
            $this->isAuthenticated = false;
194
        }
195 32
    }
196
197
    /**
198
     * @param string $responseMsg the full response XML received.
199
     * @return array
200
     */
201 20
    protected function getSessionDataFromHeader($responseMsg)
202
    {
203
        $newSessionData = [
204 20
            'sessionId' => null,
205 10
            'sequenceNumber' => null,
206
            'securityToken' => null
207 10
        ];
208
209 20
        $responseDomDoc = new \DOMDocument('1.0', 'UTF-8');
210 20
        $responseDomDoc->loadXML($responseMsg);
211 20
        $responseDomXpath = new \DOMXPath($responseDomDoc);
212 20
        $responseDomXpath->registerNamespace('awsse', 'http://xml.amadeus.com/2010/06/Session_v3');
213
214 20
        $queryTransactionStatusCode = "string(//awsse:Session/@TransactionStatusCode)";
215
216 20
        $transactionStatusCode = $responseDomXpath->evaluate($queryTransactionStatusCode);
217
218 20
        if (mb_strtolower($transactionStatusCode) !== "end") {
219 12
            $querySessionId = "string(//awsse:Session/awsse:SessionId/text())";
220 12
            $querySequenceNumber = "string(//awsse:Session/awsse:SequenceNumber/text())";
221 12
            $querySecurityToken = "string(//awsse:Session/awsse:SecurityToken/text())";
222
223 12
            $newSessionData['sessionId'] = $responseDomXpath->evaluate($querySessionId);
224 12
            $newSessionData['sequenceNumber'] = $responseDomXpath->evaluate($querySequenceNumber);
225 12
            if (!empty($newSessionData['sequenceNumber'])) {
226 12
                $newSessionData['sequenceNumber'] = (int) $newSessionData['sequenceNumber'];
227 6
            }
228 12
            $newSessionData['securityToken'] = $responseDomXpath->evaluate($querySecurityToken);
229 6
        }
230
231 20
        unset($responseDomDoc, $responseDomXpath);
232
233 20
        return $newSessionData;
234
    }
235
236
    /**
237
     * Create the Soap Headers to be used on the subsequent request.
238
     *
239
     * This depends on the current Session Data (if there is an active session) and
240
     * the Session Handler parameters (to create a new or stateless session)
241
     *
242
     * You can also terminate the session with $doEndSession = true
243
     *
244
     * @param array $sessionData
245
     * @param Client\Params\SessionHandlerParams $params
246
     * @param string $messageName
247
     * @param array $messageOptions
248
     * @return \SoapHeader[]|null
0 ignored issues
show
Documentation introduced by
Should the return type not be array?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
249
     */
250 60
    protected function createSoapHeaders($sessionData, $params, $messageName, $messageOptions)
251
    {
252 60
        $headersToSet = [];
253
254 60
        $wsdlId = $this->getWsdlIdFor($messageName);
255 60
        $wsdl = WsdlAnalyser::$wsdlIds[$wsdlId];
256
257
        //CHECK STATEFUL
258 60
        $stateful = $this->isStateful();
259
260
        //Message ID header
261 60
        array_push(
262 60
            $headersToSet,
263 60
            new \SoapHeader(
264 60
                'http://www.w3.org/2005/08/addressing',
265 60
                'MessageID',
266 60
                $this->generateGuid()
267 30
            )
268 30
        );
269
270
        //Action header
271 60
        array_push(
272 60
            $headersToSet,
273 60
            new \SoapHeader(
274 60
                'http://www.w3.org/2005/08/addressing',
275 60
                'Action',
276 60
                $this->getActionFromWsdl($wsdl, $messageName)
277 30
            )
278 30
        );
279
280
        //To header
281 60
        array_push(
282 60
            $headersToSet,
283 60
            new \SoapHeader(
284 60
                'http://www.w3.org/2005/08/addressing',
285 60
                'To',
286 60
                $this->getEndpointFromWsdl($wsdl, $messageName)
287 30
            )
288 30
        );
289
290
        //TransactionFlowLink header
291 60
        $tfl = $this->isTransactionFlowLinkEnabled();
292 60
        if ($tfl) {
293 8
            $consumerId = $this->getConsumerId(true);
294
295 8
            array_push(
296 8
                $headersToSet,
297 8
                new \SoapHeader(
298 8
                    'http://wsdl.amadeus.com/2010/06/ws/Link_v1',
299 8
                    'TransactionFlowLink',
300 8
                    new Client\Struct\HeaderV4\TransactionFlowLink($consumerId)
301 4
                )
302 4
            );
303 4
        }
304
305
        //Send authentication info headers if not authenticated and not Security_Authenticate message call
306 60
        if ($this->isAuthenticated === false && $this->isNotSecurityAuthenticateMessage($messageName)) {
307
            //Generate nonce, msg creation string & password digest:
308 44
            $password = base64_decode($params->authParams->passwordData);
309 44
            $creation = new \DateTime('now', new \DateTimeZone('UTC'));
310 44
            $t = microtime(true);
311 44
            $micro = sprintf("%03d", ($t - floor($t)) * 1000);
312 44
            $creationString = $this->createDateTimeStringForAuth($creation, $micro);
313 44
            $messageNonce = $this->generateUniqueNonce($params->authParams->nonceBase, $creationString);
314 44
            $encodedNonce = base64_encode($messageNonce);
315 44
            $digest = $this->generatePasswordDigest($password, $creationString, $messageNonce);
316
317 44
            $securityHeaderXml = $this->generateSecurityHeaderRawXml(
318 44
                $params->authParams->userId,
319 44
                $encodedNonce,
320 44
                $digest,
321 22
                $creationString
322 22
            );
323
324
            //Authentication header
325 44
            array_push(
326 44
                $headersToSet,
327 44
                new \SoapHeader(
328 44
                    'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wsswssecurity-secext-1.0.xsd',
329 44
                    'Security',
330 44
                    new \SoapVar($securityHeaderXml, XSD_ANYXML)
331 22
                )
332 22
            );
333
334 44
            if ($stateful === true) {
335
                //Not authenticated but stateful: start session!
336 8
                array_push(
337 8
                    $headersToSet,
338 8
                    new \SoapHeader(
339 8
                        'http://xml.amadeus.com/2010/06/Session_v3',
340 8
                        'Session',
341 8
                        new Client\Struct\HeaderV4\Session(
342 8
                            null,
343 4
                            self::TRANSACTION_STATUS_CODE_START
344 4
                        )
345 4
                    )
346 4
                );
347 4
            }
348
349
            //AMA_SecurityHostedUser header
350 44
            array_push(
351 44
                $headersToSet,
352 44
                new \SoapHeader(
353 44
                    'http://xml.amadeus.com/2010/06/Security_v1',
354 44
                    'AMA_SecurityHostedUser',
355 44
                    new Client\Struct\HeaderV4\SecurityHostedUser(
356 44
                        $params->authParams->officeId,
357 44
                        $params->authParams->originatorTypeCode,
358 44
                        1,
359 44
                        $params->authParams->dutyCode
360 22
                    )
361 22
                )
362 22
            );
363 38
        } elseif ($stateful === true) {
364 12
            array_push(
365 12
                $headersToSet,
366 12
                new \SoapHeader(
367 12
                    'http://xml.amadeus.com/2010/06/Session_v3',
368 12
                    'Session',
369 12
                    new Client\Struct\HeaderV4\Session(
370 12
                        $sessionData,
371 12
                        $this->getStatefulStatusCode($messageName, $messageOptions)
372 6
                    )
373 6
                )
374 6
            );
375 6
        }
376
377 60
        return $headersToSet;
378
    }
379
380
    /**
381
     * Get the Web Services server Endpoint from the WSDL.
382
     *
383
     * @param string $wsdlFilePath
384
     * @param string $messageName
385
     * @return string|null
386
     */
387 60
    protected function getEndpointFromWsdl($wsdlFilePath, $messageName)
388
    {
389 60
        $wsdlId = $this->getWsdlIdFor($messageName);
390
391 60
        return WsdlAnalyser::exaluateXpathQueryOnWsdl(
392 60
            $wsdlId,
393 60
            $wsdlFilePath,
394 30
            self::XPATH_ENDPOINT
395 30
        );
396
    }
397
398
    /**
399
     * Get the SOAPAction for a given message from the WSDL contents.
400
     *
401
     * @param string $wsdlFilePath
402
     * @param string $messageName
403
     * @return string|null
404
     */
405 60
    protected function getActionFromWsdl($wsdlFilePath, $messageName)
406
    {
407 60
        $wsdlId = $this->getWsdlIdFor($messageName);
408
409 60
        return WsdlAnalyser::exaluateXpathQueryOnWsdl(
410 60
            $wsdlId,
411 60
            $wsdlFilePath,
412 60
            sprintf(self::XPATH_OPERATION_ACTION, $messageName)
413 30
        );
414
    }
415
416
    /**
417
     * Generate a GUID
418
     *
419
     * @return string
420
     */
421 60
    protected function generateGuid()
422
    {
423 60
        mt_srand((double) microtime() * 10000);
424 60
        $charId = strtoupper(md5(uniqid(rand(), true)));
425 60
        $hyphen = chr(45); // "-"
426
427 60
        $uuid = substr($charId, 0, 8) . $hyphen
428 60
            . substr($charId, 8, 4) . $hyphen
429 60
            . substr($charId, 12, 4) . $hyphen
430 60
            . substr($charId, 16, 4) . $hyphen
431 60
            . substr($charId, 20, 12);
432
433 60
        return $uuid;
434
    }
435
436
    /**
437
     * @param string $originator
438
     * @param string $nonce
439
     * @param string $pwDigest
440
     * @param string $creationTimeString
441
     * @return string
442
     */
443 48
    protected function generateSecurityHeaderRawXml($originator, $nonce, $pwDigest, $creationTimeString)
444
    {
445
        return $xml = '<oas:Security xmlns:oas="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
0 ignored issues
show
Unused Code introduced by
$xml is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
446
	<oas:UsernameToken xmlns:oas1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" oas1:Id="UsernameToken-1">
447 48
		<oas:Username>' . $originator . '</oas:Username>
448 48
		<oas:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">' . $nonce . '</oas:Nonce>
449 48
		<oas:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">' . $pwDigest . '</oas:Password>
450 48
		<oas1:Created>' . $creationTimeString . '</oas1:Created>
451
	</oas:UsernameToken>
452 24
</oas:Security>';
453
    }
454
455
456
    /**
457
     * @param string $nonceBase
458
     * @param string $creationString
459
     * @return string
460
     */
461 44
    protected function generateUniqueNonce($nonceBase, $creationString)
462
    {
463 44
        return substr(
464 44
            sha1(
465 44
                $nonceBase . $creationString,
466 22
                true
467 22
            ),
468 44
            0,
469 22
            16
470 22
        );
471
    }
472
473
    /**
474
     * Generates a Password Digest following this algorithm:
475
     * HashedPassword = Base64(SHA-1( nonce + created + SHA-1 ( password )))
476
     * as defined in
477
     * https://webservices.amadeus.com/extranet/kdbViewDocument.do?externalId=wikidoc_web_services_embedded_security_implementation_guide_header_entries_ws-security_usernametoken&docStatus=Published&mpId=fla__1__technical
478
     *
479
     * EXAMPLE: with:
480
     *  Nonce in Base 64 = 'PZgFvh5439plJpKpIyf5ucmXhNU='
481
     *  Timestamp = '2013-01-11T09:41:03Z'
482
     *  Clear Password = 'WBSPassword'
483
     * The digest algorithm returns the Encrypted Password in Base 64:
484
     *  HshPwd = 'ic3AOJElVpvkz9ZBKd105Siry28='
485
     *
486
     * @param string $password CLEARTEXT password (NOT the base64 encoded password used in Security_Authenticate)
487
     * @param string $creationString message creation datetime
488
     *                               UTC Format: yyyy-mm-ddTHH:MM:SSZ or yyyy-mm-ddTHH:MM:SS.sssZ
489
     * @param string $messageNonce Random unique string
490
     * @return string The generated Password Digest
491
     */
492 68
    protected function generatePasswordDigest($password, $creationString, $messageNonce)
493
    {
494 68
        return base64_encode(sha1($messageNonce . $creationString . sha1($password, true), true));
495
    }
496
497
    /**
498
     * @param \DateTime $creationDateTime
499
     * @param string $micro
500
     * @return string
501
     */
502 44
    protected function createDateTimeStringForAuth($creationDateTime, $micro)
503
    {
504 44
        $creationDateTime->setTimezone(new \DateTimeZone('UTC'));
505
506 44
        return $creationDateTime->format("Y-m-d\TH:i:s:") . $micro . 'Z';
507
    }
508
509
    /**
510
     * Make SoapClient options for Soap Header 4 handler
511
     *
512
     * @return array
513
     */
514 8 View Code Duplication
    protected function makeSoapClientOptions()
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...
515
    {
516 8
        $options = $this->soapClientOptions;
517 8
        $options['classmap'] = array_merge(Classmap::$soapheader4map, Classmap::$map);
518
519 8
        if (!empty($this->params->soapClientOptions)) {
520 4
            $options = array_merge($options, $this->params->soapClientOptions);
521 2
        }
522
523 8
        return $options;
524
    }
525
526
    /**
527
     * Check is called message is not Security_Authenticate.
528
     *
529
     * @param $messageName
530
     * @return bool
531
     */
532 48
    protected function isNotSecurityAuthenticateMessage($messageName)
533
    {
534 48
        return 'Security_Authenticate' !== $messageName;
535
    }
536
537
    /**
538
     * Return transaction code for stateful requests.
539
     *
540
     * @param string $messageName name of request message (e.g. Security_Authenticate)
541
     * @param array $messageOptions
542
     * @return string
543
     */
544 12
    private function getStatefulStatusCode($messageName, array $messageOptions)
545
    {
546
        // on security-auth this is always 'Start'
547 12
        if ('Security_Authenticate' === $messageName) {
548 4
            return self::TRANSACTION_STATUS_CODE_START;
549
        }
550
551
        // if endSession is set this will be (the) 'End'
552 8
        if (isset($messageOptions['endSession']) && $messageOptions['endSession'] === true) {
553 4
            return self::TRANSACTION_STATUS_CODE_END;
554
        }
555
556
        // on everything else we assume in-series
557 4
        return self::TRANSACTION_STATUS_CODE_INSERIES;
558
    }
559
}
560