SoapHeader4::getEndpointFromWsdl()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 1
nc 1
nop 2
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
    public function setStateful($stateful)
86
    {
87
        $this->isStateful = $stateful;
88
    }
89
90
    /**
91
     * Check whether we are running in stateful mode (true) or in stateless mode (false)
92
     *
93
     * @return bool
94
     */
95
    public function isStateful()
96
    {
97
        return $this->isStateful;
98
    }
99
100
    /**
101
     * Is the TransactionFlowLink header enabled?
102
     *
103
     * @return bool
104
     */
105
    public function isTransactionFlowLinkEnabled()
106
    {
107
        return $this->enableTransactionFlowLink;
108
    }
109
110
    /**
111
     * Enable or disable TransactionFlowLink header
112
     *
113
     * @param bool $enabled
114
     */
115
    public function setTransactionFlowLink($enabled)
116
    {
117
        $this->enableTransactionFlowLink = $enabled;
118
    }
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
    public function getConsumerId($generate = false)
127
    {
128
        if (is_null($this->consumerId) && $generate) {
129
            $this->consumerId = $this->generateGuid();
130
        }
131
132
        return $this->consumerId;
133
    }
134
135
    /**
136
     * Set the TransactionFlowLink Consumer ID
137
     *
138
     * @param string $id
139
     * @return void
140
     */
141
    public function setConsumerId($id)
142
    {
143
        $this->consumerId = $id;
144
    }
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
    protected function prepareForNextMessage($messageName, $messageOptions)
158
    {
159
        if ($this->isAuthenticated === true && is_int($this->sessionData['sequenceNumber'])) {
160
            $this->sessionData['sequenceNumber']++;
161
        }
162
163
        $headers = $this->createSoapHeaders($this->sessionData, $this->params, $messageName, $messageOptions);
164
165
        $this->getSoapClient($messageName)->__setSoapHeaders(null);
166
        $this->getSoapClient($messageName)->__setSoapHeaders($headers);
167
    }
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
    protected function handlePostMessage($messageName, $lastResponse, $messageOptions, $result)
184
    {
185
        //CHECK FOR SESSION DATA:
186
        if ($this->isStateful() === true) {
187
            //We need to extract session info
188
            $this->sessionData = $this->getSessionDataFromHeader($lastResponse);
189
            $this->isAuthenticated = (!empty($this->sessionData['sessionId']) &&
190
                !empty($this->sessionData['sequenceNumber']) &&
191
                !empty($this->sessionData['securityToken']));
192
        } else {
193
            $this->isAuthenticated = false;
194
        }
195
    }
196
197
    /**
198
     * @param string $responseMsg the full response XML received.
199
     * @return array
200
     */
201
    protected function getSessionDataFromHeader($responseMsg)
202
    {
203
        $newSessionData = [
204
            'sessionId' => null,
205
            'sequenceNumber' => null,
206
            'securityToken' => null
207
        ];
208
209
        $responseDomDoc = new \DOMDocument('1.0', 'UTF-8');
210
        $responseDomDoc->loadXML($responseMsg);
211
        $responseDomXpath = new \DOMXPath($responseDomDoc);
212
        $responseDomXpath->registerNamespace('awsse', 'http://xml.amadeus.com/2010/06/Session_v3');
213
214
        $queryTransactionStatusCode = "string(//awsse:Session/@TransactionStatusCode)";
215
216
        $transactionStatusCode = $responseDomXpath->evaluate($queryTransactionStatusCode);
217
218
        if (mb_strtolower($transactionStatusCode) !== "end") {
219
            $querySessionId = "string(//awsse:Session/awsse:SessionId/text())";
220
            $querySequenceNumber = "string(//awsse:Session/awsse:SequenceNumber/text())";
221
            $querySecurityToken = "string(//awsse:Session/awsse:SecurityToken/text())";
222
223
            $newSessionData['sessionId'] = $responseDomXpath->evaluate($querySessionId);
224
            $newSessionData['sequenceNumber'] = $responseDomXpath->evaluate($querySequenceNumber);
225
            if (!empty($newSessionData['sequenceNumber'])) {
226
                $newSessionData['sequenceNumber'] = (int) $newSessionData['sequenceNumber'];
227
            }
228
            $newSessionData['securityToken'] = $responseDomXpath->evaluate($querySecurityToken);
229
        }
230
231
        unset($responseDomDoc, $responseDomXpath);
232
233
        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
249
     */
250
    protected function createSoapHeaders($sessionData, $params, $messageName, $messageOptions)
251
    {
252
        $headersToSet = [];
253
254
        $wsdlId = $this->getWsdlIdFor($messageName);
255
        $wsdl = WsdlAnalyser::$wsdlIds[$wsdlId];
256
257
        //CHECK STATEFUL
258
        $stateful = $this->isStateful();
259
260
        //Message ID header
261
        array_push(
262
            $headersToSet,
263
            new \SoapHeader(
264
                'http://www.w3.org/2005/08/addressing',
265
                'MessageID',
266
                $this->generateGuid()
267
            )
268
        );
269
270
        //Action header
271
        array_push(
272
            $headersToSet,
273
            new \SoapHeader(
274
                'http://www.w3.org/2005/08/addressing',
275
                'Action',
276
                $this->getActionFromWsdl($wsdl, $messageName)
277
            )
278
        );
279
280
        //To header
281
        array_push(
282
            $headersToSet,
283
            new \SoapHeader(
284
                'http://www.w3.org/2005/08/addressing',
285
                'To',
286
                $this->getEndpointFromWsdl($wsdl, $messageName)
287
            )
288
        );
289
290
        //TransactionFlowLink header
291
        $tfl = $this->isTransactionFlowLinkEnabled();
292
        if ($tfl) {
293
            $consumerId = $this->getConsumerId(true);
294
295
            array_push(
296
                $headersToSet,
297
                new \SoapHeader(
298
                    'http://wsdl.amadeus.com/2010/06/ws/Link_v1',
299
                    'TransactionFlowLink',
300
                    new Client\Struct\HeaderV4\TransactionFlowLink($consumerId)
301
                )
302
            );
303
        }
304
305
        //Send authentication info headers if not authenticated and not Security_Authenticate message call
306
        if ($this->isAuthenticated === false && $this->isNotSecurityAuthenticateMessage($messageName)) {
307
            //Generate nonce, msg creation string & password digest:
308
            $password = base64_decode($params->authParams->passwordData);
309
            $creation = new \DateTime('now', new \DateTimeZone('UTC'));
310
            $t = microtime(true);
311
            $micro = sprintf("%03d", ($t - floor($t)) * 1000);
312
            $creationString = $this->createDateTimeStringForAuth($creation, $micro);
313
            $messageNonce = $this->generateUniqueNonce($params->authParams->nonceBase, $creationString);
314
            $encodedNonce = base64_encode($messageNonce);
315
            $digest = $this->generatePasswordDigest($password, $creationString, $messageNonce);
316
317
            $securityHeaderXml = $this->generateSecurityHeaderRawXml(
318
                $params->authParams->userId,
319
                $encodedNonce,
320
                $digest,
321
                $creationString
322
            );
323
324
            //Authentication header
325
            array_push(
326
                $headersToSet,
327
                new \SoapHeader(
328
                    'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wsswssecurity-secext-1.0.xsd',
329
                    'Security',
330
                    new \SoapVar($securityHeaderXml, XSD_ANYXML)
331
                )
332
            );
333
334
            if ($stateful === true) {
335
                //Not authenticated but stateful: start session!
336
                array_push(
337
                    $headersToSet,
338
                    new \SoapHeader(
339
                        'http://xml.amadeus.com/2010/06/Session_v3',
340
                        'Session',
341
                        new Client\Struct\HeaderV4\Session(
342
                            null,
343
                            self::TRANSACTION_STATUS_CODE_START
344
                        )
345
                    )
346
                );
347
            }
348
349
            //AMA_SecurityHostedUser header
350
            array_push(
351
                $headersToSet,
352
                new \SoapHeader(
353
                    'http://xml.amadeus.com/2010/06/Security_v1',
354
                    'AMA_SecurityHostedUser',
355
                    new Client\Struct\HeaderV4\SecurityHostedUser(
356
                        $params->authParams->officeId,
357
                        $params->authParams->originatorTypeCode,
358
                        1,
359
                        $params->authParams->dutyCode
360
                    )
361
                )
362
            );
363
        } elseif ($stateful === true) {
364
            array_push(
365
                $headersToSet,
366
                new \SoapHeader(
367
                    'http://xml.amadeus.com/2010/06/Session_v3',
368
                    'Session',
369
                    new Client\Struct\HeaderV4\Session(
370
                        $sessionData,
371
                        $this->getStatefulStatusCode($messageName, $messageOptions)
372
                    )
373
                )
374
            );
375
        }
376
377
        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
    protected function getEndpointFromWsdl($wsdlFilePath, $messageName)
388
    {
389
        $wsdlId = $this->getWsdlIdFor($messageName);
390
391
        return WsdlAnalyser::exaluateXpathQueryOnWsdl(
392
            $wsdlId,
393
            $wsdlFilePath,
394
            self::XPATH_ENDPOINT
395
        );
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
    protected function getActionFromWsdl($wsdlFilePath, $messageName)
406
    {
407
        $wsdlId = $this->getWsdlIdFor($messageName);
408
409
        return WsdlAnalyser::exaluateXpathQueryOnWsdl(
410
            $wsdlId,
411
            $wsdlFilePath,
412
            sprintf(self::XPATH_OPERATION_ACTION, $messageName)
413
        );
414
    }
415
416
    /**
417
     * Generate a GUID
418
     *
419
     * @return string
420
     */
421
    protected function generateGuid()
422
    {
423
        mt_srand((double) microtime() * 10000);
424
        $charId = strtoupper(md5(uniqid(rand(), true)));
425
        $hyphen = chr(45); // "-"
426
427
        $uuid = substr($charId, 0, 8) . $hyphen
428
            . substr($charId, 8, 4) . $hyphen
429
            . substr($charId, 12, 4) . $hyphen
430
            . substr($charId, 16, 4) . $hyphen
431
            . substr($charId, 20, 12);
432
433
        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
    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
		<oas:Username>' . $originator . '</oas:Username>
448
		<oas:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">' . $nonce . '</oas:Nonce>
449
		<oas:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">' . $pwDigest . '</oas:Password>
450
		<oas1:Created>' . $creationTimeString . '</oas1:Created>
451
	</oas:UsernameToken>
452
</oas:Security>';
453
    }
454
455
456
    /**
457
     * @param string $nonceBase
458
     * @param string $creationString
459
     * @return string
460
     */
461
    protected function generateUniqueNonce($nonceBase, $creationString)
462
    {
463
        return substr(
464
            sha1(
465
                $nonceBase . $creationString,
466
                true
467
            ),
468
            0,
469
            16
470
        );
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
    protected function generatePasswordDigest($password, $creationString, $messageNonce)
493
    {
494
        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
    protected function createDateTimeStringForAuth($creationDateTime, $micro)
503
    {
504
        $creationDateTime->setTimezone(new \DateTimeZone('UTC'));
505
506
        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 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
        $options = $this->soapClientOptions;
517
        $options['classmap'] = array_merge(Classmap::$soapheader4map, Classmap::$map);
518
519
        if (!empty($this->params->soapClientOptions)) {
520
            $options = array_merge($options, $this->params->soapClientOptions);
521
        }
522
523
        return $options;
524
    }
525
526
    /**
527
     * Check is called message is not Security_Authenticate.
528
     *
529
     * @param $messageName
530
     * @return bool
531
     */
532
    protected function isNotSecurityAuthenticateMessage($messageName)
533
    {
534
        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
    private function getStatefulStatusCode($messageName, array $messageOptions)
545
    {
546
        // on security-auth this is always 'Start'
547
        if ('Security_Authenticate' === $messageName) {
548
            return self::TRANSACTION_STATUS_CODE_START;
549
        }
550
551
        // if endSession is set this will be (the) 'End'
552
        if (isset($messageOptions['endSession']) && $messageOptions['endSession'] === true) {
553
            return self::TRANSACTION_STATUS_CODE_END;
554
        }
555
556
        // on everything else we assume in-series
557
        return self::TRANSACTION_STATUS_CODE_INSERIES;
558
    }
559
}
560