Completed
Push — master ( 7f57a1...b420d4 )
by Dieter
71:04 queued 41:59
created

SoapHeader4::isNotSecurityAuthenticateMessage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
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
        //Send authentication info headers if not authenticated and not Security_Authenticate message call
289 52
        if ($this->isAuthenticated === false && $this->isNotSecurityAuthenticateMessage($messageName)) {
290
            //Generate nonce, msg creation string & password digest:
291 44
            $password = base64_decode($params->authParams->passwordData);
292 44
            $creation = new \DateTime('now', new \DateTimeZone('UTC'));
293 44
            $t = microtime(true);
294 44
            $micro = sprintf("%03d", ($t - floor($t)) * 1000);
295 44
            $creationString = $this->createDateTimeStringForAuth($creation, $micro);
296 44
            $messageNonce = $this->generateUniqueNonce($params->authParams->nonceBase, $creationString);
297 44
            $encodedNonce = base64_encode($messageNonce);
298 44
            $digest = $this->generatePasswordDigest($password, $creationString, $messageNonce);
299
300 44
            $securityHeaderXml = $this->generateSecurityHeaderRawXml(
301 44
                $params->authParams->userId,
302 44
                $encodedNonce,
303 44
                $digest,
304 22
                $creationString
305 22
            );
306
307
            //Authentication header
308 44
            array_push(
309 44
                $headersToSet,
310 44
                new \SoapHeader(
311 44
                    'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wsswssecurity-secext-1.0.xsd',
312 44
                    'Security',
313 44
                    new \SoapVar($securityHeaderXml, XSD_ANYXML)
314 22
                )
315 22
            );
316
317 44
            if ($stateful === true) {
318
                //Not authenticated but stateful: start session!
319 8
                array_push(
320 8
                    $headersToSet,
321 8
                    new \SoapHeader(
322 8
                        'http://xml.amadeus.com/2010/06/Session_v3',
323 8
                        'Session',
324 8
                        new Client\Struct\HeaderV4\Session(
325 8
                            null,
326 4
                            "Start"
327 4
                        )
328 4
                    )
329 4
                );
330 4
            }
331
332
            //AMA_SecurityHostedUser header
333 44
            array_push(
334 44
                $headersToSet,
335 44
                new \SoapHeader(
336 44
                    'http://xml.amadeus.com/2010/06/Security_v1',
337 44
                    'AMA_SecurityHostedUser',
338 44
                    new Client\Struct\HeaderV4\SecurityHostedUser(
339 44
                        $params->authParams->officeId,
340 44
                        $params->authParams->originatorTypeCode,
341 44
                        1,
342 44
                        $params->authParams->dutyCode
343 22
                    )
344 22
                )
345 22
            );
346 30
        } elseif ($stateful === true) {
347
            //We are authenticated and stateful: provide session header to continue or terminate session
348
            $statusCode =
349 4
                (isset($messageOptions['endSession']) && $messageOptions['endSession'] === true) ?
350 4
                    "End" : "InSeries";
351
352 4
            array_push(
353 4
                $headersToSet,
354 4
                new \SoapHeader(
355 4
                    'http://xml.amadeus.com/2010/06/Session_v3',
356 4
                    'Session',
357 4
                    new Client\Struct\HeaderV4\Session(
358 4
                        $sessionData,
359 2
                        $statusCode
360 2
                    )
361 2
                )
362 2
            );
363 2
        }
364
365 52
        return $headersToSet;
366
    }
367
368
    /**
369
     * Get the Web Services server Endpoint from the WSDL.
370
     *
371
     * @param string $wsdlFilePath
372
     * @param string $messageName
373
     * @return string|null
374
     */
375 52
    protected function getEndpointFromWsdl($wsdlFilePath, $messageName)
376
    {
377 52
        $wsdlId = $this->getWsdlIdFor($messageName);
378
379 52
        return WsdlAnalyser::exaluateXpathQueryOnWsdl(
380 52
            $wsdlId,
381 52
            $wsdlFilePath,
382 26
            self::XPATH_ENDPOINT
383 26
        );
384
    }
385
386
    /**
387
     * Get the SOAPAction for a given message from the WSDL contents.
388
     *
389
     * @param string $wsdlFilePath
390
     * @param string $messageName
391
     * @return string|null
392
     */
393 52
    protected function getActionFromWsdl($wsdlFilePath, $messageName)
394
    {
395 52
        $wsdlId = $this->getWsdlIdFor($messageName);
396
397 52
        return WsdlAnalyser::exaluateXpathQueryOnWsdl(
398 52
            $wsdlId,
399 52
            $wsdlFilePath,
400 52
            sprintf(self::XPATH_OPERATION_ACTION, $messageName)
401 26
        );
402
    }
403
404
    /**
405
     * Generate a GUID
406
     *
407
     * @return string
408
     */
409 52
    protected function generateGuid()
410
    {
411 52
        mt_srand((double) microtime() * 10000);
412 52
        $charId = strtoupper(md5(uniqid(rand(), true)));
413 52
        $hyphen = chr(45); // "-"
414
415 52
        $uuid = substr($charId, 0, 8) . $hyphen
416 52
            . substr($charId, 8, 4) . $hyphen
417 52
            . substr($charId, 12, 4) . $hyphen
418 52
            . substr($charId, 16, 4) . $hyphen
419 52
            . substr($charId, 20, 12);
420
421 52
        return $uuid;
422
    }
423
424
    /**
425
     * @param string $originator
426
     * @param string $nonce
427
     * @param string $pwDigest
428
     * @param string $creationTimeString
429
     * @return string
430
     */
431 48
    protected function generateSecurityHeaderRawXml($originator, $nonce, $pwDigest, $creationTimeString)
432
    {
433
        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...
434
	<oas:UsernameToken xmlns:oas1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" oas1:Id="UsernameToken-1">
435 48
		<oas:Username>' . $originator . '</oas:Username>
436 48
		<oas:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">' . $nonce . '</oas:Nonce>
437 48
		<oas:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">' . $pwDigest . '</oas:Password>
438 48
		<oas1:Created>' . $creationTimeString . '</oas1:Created>
439
	</oas:UsernameToken>
440 24
</oas:Security>';
441
    }
442
443
444
    /**
445
     * @param string $nonceBase
446
     * @param string $creationString
447
     * @return string
448
     */
449 44
    protected function generateUniqueNonce($nonceBase, $creationString)
450
    {
451 44
        return substr(
452 44
            sha1(
453 44
                $nonceBase . $creationString,
454 22
                true
455 22
            ),
456 44
            0,
457 22
            16
458 22
        );
459
    }
460
461
    /**
462
     * Generates a Password Digest following this algorithm:
463
     * HashedPassword = Base64(SHA-1( nonce + created + SHA-1 ( password )))
464
     * as defined in
465
     * 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
466
     *
467
     * EXAMPLE: with:
468
     *  Nonce in Base 64 = 'PZgFvh5439plJpKpIyf5ucmXhNU='
469
     *  Timestamp = '2013-01-11T09:41:03Z'
470
     *  Clear Password = 'WBSPassword'
471
     * The digest algorithm returns the Encrypted Password in Base 64:
472
     *  HshPwd = 'ic3AOJElVpvkz9ZBKd105Siry28='
473
     *
474
     * @param string $password CLEARTEXT password (NOT the base64 encoded password used in Security_Authenticate)
475
     * @param string $creationString message creation datetime
476
     *                               UTC Format: yyyy-mm-ddTHH:MM:SSZ or yyyy-mm-ddTHH:MM:SS.sssZ
477
     * @param string $messageNonce Random unique string
478
     * @return string The generated Password Digest
479
     */
480 68
    protected function generatePasswordDigest($password, $creationString, $messageNonce)
481
    {
482 68
        return base64_encode(sha1($messageNonce . $creationString . sha1($password, true), true));
483
    }
484
485
    /**
486
     * @param \DateTime $creationDateTime
487
     * @param string $micro
488
     * @return string
489
     */
490 44
    protected function createDateTimeStringForAuth($creationDateTime, $micro)
491
    {
492 44
        $creationDateTime->setTimezone(new \DateTimeZone('UTC'));
493
494 44
        return $creationDateTime->format("Y-m-d\TH:i:s:") . $micro . 'Z';
495
    }
496
497
    /**
498
     * Make SoapClient options for Soap Header 4 handler
499
     *
500
     * @return array
501
     */
502 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...
503
    {
504 8
        $options = $this->soapClientOptions;
505 8
        $options['classmap'] = array_merge(Classmap::$soapheader4map, Classmap::$map);
506
507 8
        if (!empty($this->params->soapClientOptions)) {
508 4
            $options = array_merge($options, $this->params->soapClientOptions);
509 2
        }
510
511 8
        return $options;
512
    }
513
514
    /**
515
     * Check is called message is not Security_Authenticate.
516
     *
517
     * @param $messageName
518
     * @return bool
519
     */
520 44
    protected function isNotSecurityAuthenticateMessage($messageName)
521
    {
522 44
        return 'Security_Authenticate' !== $messageName;
523
    }
524
}
525