Completed
Pull Request — master (#234)
by
unknown
08:41
created

SoapHeader4::setStateful()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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
    /**
50
     * Switch between stateful & stateless sessions. Default: stateful
51
     *
52
     * @var bool
53
     */
54
    protected $isStateful = true;
55
56
    protected $enableTransactionFlowLink = false;
57
58
    /**
59
     * TransactionFlowLink Consumer ID
60
     *
61
     * @var string|null
62
     */
63
    protected $consumerId;
64
65
    /**
66
     * @param bool $stateful
67
     */
68 184
    public function setStateful($stateful)
69
    {
70 184
        $this->isStateful = $stateful;
71 184
    }
72
73
    /**
74
     * Check whether we are running in stateful mode (true) or in stateless mode (false)
75
     *
76
     * @return bool
77
     */
78 76
    public function isStateful()
79
    {
80 76
        return $this->isStateful;
81
    }
82
83
    /**
84
     * Is the TransactionFlowLink header enabled?
85
     *
86
     * @return bool
87
     */
88 52
    public function isTransactionFlowLinkEnabled()
89
    {
90 52
        return $this->enableTransactionFlowLink;
91
    }
92
93
    /**
94
     * Enable or disable TransactionFlowLink header
95
     *
96
     * @param bool $enabled
97
     */
98 184
    public function setTransactionFlowLink($enabled)
99
    {
100 184
        $this->enableTransactionFlowLink = $enabled;
101 184
    }
102
103
    /**
104
     * Get the TransactionFlowLink Consumer ID
105
     *
106
     * @param bool $generate Whether to generate a consumer ID
107
     * @return string|null
108
     */
109 12
    public function getConsumerId($generate = false)
110
    {
111 12
        if (is_null($this->consumerId) && $generate) {
112 4
            $this->consumerId = $this->generateGuid();
113 2
        }
114
115 12
        return $this->consumerId;
116
    }
117
118
    /**
119
     * Set the TransactionFlowLink Consumer ID
120
     *
121
     * @param string $id
122
     * @return void
123
     */
124 184
    public function setConsumerId($id)
125
    {
126 184
        $this->consumerId = $id;
127 184
    }
128
129
    /**
130
     * Handles authentication & sessions
131
     *
132
     * If authenticated, increment sequence number for next message and set session info to soapheader
133
     * If not, set auth info to soapheader
134
     *
135
     * @uses $this->isAuthenticated
136
     * @uses $this->sessionData
137
     * @param string $messageName
138
     * @param array $messageOptions
139
     */
140 32
    protected function prepareForNextMessage($messageName, $messageOptions)
141
    {
142 32
        if ($this->isAuthenticated === true && is_int($this->sessionData['sequenceNumber'])) {
143 4
            $this->sessionData['sequenceNumber']++;
144 2
        }
145
146 32
        $headers = $this->createSoapHeaders($this->sessionData, $this->params, $messageName, $messageOptions);
147
148 32
        $this->getSoapClient($messageName)->__setSoapHeaders(null);
149 32
        $this->getSoapClient($messageName)->__setSoapHeaders($headers);
150 32
    }
151
152
153
    /**
154
     * Handles post message actions
155
     *
156
     * - look for session info and set status variables
157
     * - checks for message errors?
158
     * - ends terminated sessions
159
     *
160
     * @param string $messageName
161
     * @param string $lastResponse
162
     * @param array $messageOptions
163
     * @param mixed $result
164
     * @return void
165
     */
166 32
    protected function handlePostMessage($messageName, $lastResponse, $messageOptions, $result)
167
    {
168
        //CHECK FOR SESSION DATA:
169 32
        if ($this->isStateful() === true) {
170
            //We need to extract session info
171 12
            $this->sessionData = $this->getSessionDataFromHeader($lastResponse);
172 12
            $this->isAuthenticated = (!empty($this->sessionData['sessionId']) &&
173 12
                !empty($this->sessionData['sequenceNumber']) &&
174 10
                !empty($this->sessionData['securityToken']));
175 6
        } else {
176 20
            $this->isAuthenticated = false;
177
        }
178 32
    }
179
180
    /**
181
     * @param string $responseMsg the full response XML received.
182
     * @return array
183
     */
184 20
    protected function getSessionDataFromHeader($responseMsg)
185
    {
186
        $newSessionData = [
187 20
            'sessionId' => null,
188 10
            'sequenceNumber' => null,
189
            'securityToken' => null
190 10
        ];
191
192 20
        $responseDomDoc = new \DOMDocument('1.0', 'UTF-8');
193 20
        $responseDomDoc->loadXML($responseMsg);
194 20
        $responseDomXpath = new \DOMXPath($responseDomDoc);
195 20
        $responseDomXpath->registerNamespace('awsse', 'http://xml.amadeus.com/2010/06/Session_v3');
196
197 20
        $queryTransactionStatusCode = "string(//awsse:Session/@TransactionStatusCode)";
198
199 20
        $transactionStatusCode = $responseDomXpath->evaluate($queryTransactionStatusCode);
200
201 20
        if (mb_strtolower($transactionStatusCode) !== "end") {
202 12
            $querySessionId = "string(//awsse:Session/awsse:SessionId/text())";
203 12
            $querySequenceNumber = "string(//awsse:Session/awsse:SequenceNumber/text())";
204 12
            $querySecurityToken = "string(//awsse:Session/awsse:SecurityToken/text())";
205
206 12
            $newSessionData['sessionId'] = $responseDomXpath->evaluate($querySessionId);
207 12
            $newSessionData['sequenceNumber'] = $responseDomXpath->evaluate($querySequenceNumber);
208 12
            if (!empty($newSessionData['sequenceNumber'])) {
209 12
                $newSessionData['sequenceNumber'] = (int) $newSessionData['sequenceNumber'];
210 6
            }
211 12
            $newSessionData['securityToken'] = $responseDomXpath->evaluate($querySecurityToken);
212 6
        }
213
214 20
        unset($responseDomDoc, $responseDomXpath);
215
216 20
        return $newSessionData;
217
    }
218
219
    /**
220
     * Create the Soap Headers to be used on the subsequent request.
221
     *
222
     * This depends on the current Session Data (if there is an active session) and
223
     * the Session Handler parameters (to create a new or stateless session)
224
     *
225
     * You can also terminate the session with $doEndSession = true
226
     *
227
     * @param array $sessionData
228
     * @param Client\Params\SessionHandlerParams $params
229
     * @param string $messageName
230
     * @param array $messageOptions
231
     * @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...
232
     */
233 52
    protected function createSoapHeaders($sessionData, $params, $messageName, $messageOptions)
234
    {
235 52
        $headersToSet = [];
236
237 52
        $wsdlId = $this->getWsdlIdFor($messageName);
238 52
        $wsdl = WsdlAnalyser::$wsdlIds[$wsdlId];
239
240
        //CHECK STATEFUL
241 52
        $stateful = $this->isStateful();
242
243
        //Message ID header
244 52
        array_push(
245 52
            $headersToSet,
246 52
            new \SoapHeader(
247 52
                'http://www.w3.org/2005/08/addressing',
248 52
                'MessageID',
249 52
                $this->generateGuid()
250 26
            )
251 26
        );
252
253
        //Action header
254 52
        array_push(
255 52
            $headersToSet,
256 52
            new \SoapHeader(
257 52
                'http://www.w3.org/2005/08/addressing',
258 52
                'Action',
259 52
                $this->getActionFromWsdl($wsdl, $messageName)
260 26
            )
261 26
        );
262
263
        //To header
264 52
        array_push(
265 52
            $headersToSet,
266 52
            new \SoapHeader(
267 52
                'http://www.w3.org/2005/08/addressing',
268 52
                'To',
269 52
                $this->getEndpointFromWsdl($wsdl, $messageName)
270 26
            )
271 26
        );
272
273
        //TransactionFlowLink header
274 52
        $tfl = $this->isTransactionFlowLinkEnabled();
275 52
        if ($tfl) {
276 8
            $consumerId = $this->getConsumerId(true);
277
278 8
            array_push(
279 8
                $headersToSet,
280 8
                new \SoapHeader(
281 8
                    'http://wsdl.amadeus.com/2010/06/ws/Link_v1',
282 8
                    'TransactionFlowLink',
283 8
                    new Client\Struct\HeaderV4\TransactionFlowLink($consumerId)
284 4
                )
285 4
            );
286 4
        }
287
288
        // If not Security_Authenticate message - send authentication information in headers
289 52
        if ($this->isNotSecurityAuthenticateMessage($messageName)) {
290
            //Send authentication info
291 52
            if ($this->isAuthenticated === false) {
292
                //Generate nonce, msg creation string & password digest:
293 44
                $password = base64_decode($params->authParams->passwordData);
294 44
                $creation = new \DateTime('now', new \DateTimeZone('UTC'));
295 44
                $t = microtime(true);
296 44
                $micro = sprintf("%03d", ($t - floor($t)) * 1000);
297 44
                $creationString = $this->createDateTimeStringForAuth($creation, $micro);
298 44
                $messageNonce = $this->generateUniqueNonce($params->authParams->nonceBase, $creationString);
299 44
                $encodedNonce = base64_encode($messageNonce);
300 44
                $digest = $this->generatePasswordDigest($password, $creationString, $messageNonce);
301
302 44
                $securityHeaderXml = $this->generateSecurityHeaderRawXml(
303 44
                    $params->authParams->userId,
304 44
                    $encodedNonce,
305 44
                    $digest,
306 22
                    $creationString
307 22
                );
308
309
                //Authentication header
310 44
                array_push(
311 44
                    $headersToSet,
312 44
                    new \SoapHeader(
313 44
                        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wsswssecurity-secext-1.0.xsd',
314 44
                        'Security',
315 44
                        new \SoapVar($securityHeaderXml, XSD_ANYXML)
316 22
                    )
317 22
                );
318
319 44
                if ($stateful === true) {
320
                    //Not authenticated but stateful: start session!
321 8
                    array_push(
322 8
                        $headersToSet,
323 8
                        new \SoapHeader(
324 8
                            'http://xml.amadeus.com/2010/06/Session_v3',
325 8
                            'Session',
326 8
                            new Client\Struct\HeaderV4\Session(
327 8
                                null,
328 4
                                "Start"
329 4
                            )
330 4
                        )
331 4
                    );
332 4
                }
333
334
                //AMA_SecurityHostedUser header
335 44
                array_push(
336 44
                    $headersToSet,
337 44
                    new \SoapHeader(
338 44
                        'http://xml.amadeus.com/2010/06/Security_v1',
339 44
                        'AMA_SecurityHostedUser',
340 44
                        new Client\Struct\HeaderV4\SecurityHostedUser(
341 44
                            $params->authParams->officeId,
342 44
                            $params->authParams->originatorTypeCode,
343 44
                            1,
344 44
                            $params->authParams->dutyCode
345 22
                        )
346 22
                    )
347 22
                );
348 30
            } elseif ($stateful === true) {
349
                //We are authenticated and stateful: provide session header to continue or terminate session
350
                $statusCode =
351 4
                    (isset($messageOptions['endSession']) && $messageOptions['endSession'] === true) ?
352 4
                        "End" : "InSeries";
353
354 4
                array_push(
355 4
                    $headersToSet,
356 4
                    new \SoapHeader(
357 4
                        'http://xml.amadeus.com/2010/06/Session_v3',
358 4
                        'Session',
359 4
                        new Client\Struct\HeaderV4\Session(
360 4
                            $sessionData,
361 2
                            $statusCode
362 2
                        )
363 2
                    )
364 2
                );
365 2
            }
366 26
        }
367
368 52
        return $headersToSet;
369
    }
370
371
    /**
372
     * Get the Web Services server Endpoint from the WSDL.
373
     *
374
     * @param string $wsdlFilePath
375
     * @param string $messageName
376
     * @return string|null
377
     */
378 52
    protected function getEndpointFromWsdl($wsdlFilePath, $messageName)
379
    {
380 52
        $wsdlId = $this->getWsdlIdFor($messageName);
381
382 52
        return WsdlAnalyser::exaluateXpathQueryOnWsdl(
383 52
            $wsdlId,
384 52
            $wsdlFilePath,
385 26
            self::XPATH_ENDPOINT
386 26
        );
387
    }
388
389
    /**
390
     * Get the SOAPAction for a given message from the WSDL contents.
391
     *
392
     * @param string $wsdlFilePath
393
     * @param string $messageName
394
     * @return string|null
395
     */
396 52
    protected function getActionFromWsdl($wsdlFilePath, $messageName)
397
    {
398 52
        $wsdlId = $this->getWsdlIdFor($messageName);
399
400 52
        return WsdlAnalyser::exaluateXpathQueryOnWsdl(
401 52
            $wsdlId,
402 52
            $wsdlFilePath,
403 52
            sprintf(self::XPATH_OPERATION_ACTION, $messageName)
404 26
        );
405
    }
406
407
    /**
408
     * Generate a GUID
409
     *
410
     * @return string
411
     */
412 52
    protected function generateGuid()
413
    {
414 52
        mt_srand((double) microtime() * 10000);
415 52
        $charId = strtoupper(md5(uniqid(rand(), true)));
416 52
        $hyphen = chr(45); // "-"
417
418 52
        $uuid = substr($charId, 0, 8).$hyphen
419 52
            .substr($charId, 8, 4).$hyphen
420 52
            .substr($charId, 12, 4).$hyphen
421 52
            .substr($charId, 16, 4).$hyphen
422 52
            .substr($charId, 20, 12);
423
424 52
        return $uuid;
425
    }
426
427
    /**
428
     * @param string $originator
429
     * @param string $nonce
430
     * @param string $pwDigest
431
     * @param string $creationTimeString
432
     * @return string
433
     */
434 48
    protected function generateSecurityHeaderRawXml($originator, $nonce, $pwDigest, $creationTimeString)
435
    {
436
        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...
437
	<oas:UsernameToken xmlns:oas1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" oas1:Id="UsernameToken-1">
438 48
		<oas:Username>' . $originator . '</oas:Username>
439 48
		<oas:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">' . $nonce . '</oas:Nonce>
440 48
		<oas:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">' . $pwDigest . '</oas:Password>
441 48
		<oas1:Created>' . $creationTimeString . '</oas1:Created>
442
	</oas:UsernameToken>
443 24
</oas:Security>';
444
    }
445
446
447
    /**
448
     * @param string $nonceBase
449
     * @param string $creationString
450
     * @return string
451
     */
452 44
    protected function generateUniqueNonce($nonceBase, $creationString)
453
    {
454 44
        return substr(
455 44
            sha1(
456 44
                $nonceBase . $creationString,
457 22
                true
458 22
            ),
459 44
            0,
460 22
            16
461 22
        );
462
    }
463
464
    /**
465
     * Generates a Password Digest following this algorithm:
466
     * HashedPassword = Base64(SHA-1( nonce + created + SHA-1 ( password )))
467
     * as defined in
468
     * 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
469
     *
470
     * EXAMPLE: with:
471
     *  Nonce in Base 64 = 'PZgFvh5439plJpKpIyf5ucmXhNU='
472
     *  Timestamp = '2013-01-11T09:41:03Z'
473
     *  Clear Password = 'WBSPassword'
474
     * The digest algorithm returns the Encrypted Password in Base 64:
475
     *  HshPwd = 'ic3AOJElVpvkz9ZBKd105Siry28='
476
     *
477
     * @param string $password CLEARTEXT password (NOT the base64 encoded password used in Security_Authenticate)
478
     * @param string $creationString message creation datetime
479
     *                               UTC Format: yyyy-mm-ddTHH:MM:SSZ or yyyy-mm-ddTHH:MM:SS.sssZ
480
     * @param string $messageNonce Random unique string
481
     * @return string The generated Password Digest
482
     */
483 68
    protected function generatePasswordDigest($password, $creationString, $messageNonce)
484
    {
485 68
        return base64_encode(sha1($messageNonce . $creationString . sha1($password, true), true));
486
    }
487
488
    /**
489
     * @param \DateTime $creationDateTime
490
     * @param string $micro
491
     * @return string
492
     */
493 44
    protected function createDateTimeStringForAuth($creationDateTime, $micro)
494
    {
495 44
        $creationDateTime->setTimezone(new \DateTimeZone('UTC'));
496 44
        return $creationDateTime->format("Y-m-d\TH:i:s:") . $micro . 'Z';
497
    }
498
499
    /**
500
     * Make SoapClient options for Soap Header 4 handler
501
     *
502
     * @return array
503
     */
504 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...
505
    {
506 8
        $options = $this->soapClientOptions;
507 8
        $options['classmap'] = array_merge(Classmap::$soapheader4map, Classmap::$map);
508
509 8
        if (!empty($this->params->soapClientOptions)) {
510 4
            $options = array_merge($options, $this->params->soapClientOptions);
511 2
        }
512
513 8
        return $options;
514
    }
515
516
    /**
517
     * Check is called message is not Security_Authenticate.
518
     *
519
     * @param $messageName
520
     * @return bool
521
     */
522 52
    protected function isNotSecurityAuthenticateMessage($messageName)
523
    {
524 52
        return 'Security_Authenticate' !== $messageName;
525
    }
526
}
527