Completed
Push — master ( dcb951...0b0e41 )
by Dieter
07:13
created

SoapHeader4::makeSoapClientOptions()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 11
Ratio 100 %

Importance

Changes 0
Metric Value
dl 11
loc 11
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 0
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
    /**
57
     * @param bool $stateful
58
     */
59
    public function setStateful($stateful)
60
    {
61
        $this->isStateful = $stateful;
62
    }
63
64
    /**
65
     * Check whether we are running in stateful mode (true) or in stateless mode (false)
66
     *
67
     * @return bool
68
     */
69
    public function isStateful()
70
    {
71
        return $this->isStateful;
72
    }
73
74
    /**
75
     * Handles authentication & sessions
76
     *
77
     * If authenticated, increment sequence number for next message and set session info to soapheader
78
     * If not, set auth info to soapheader
79
     *
80
     * @uses $this->isAuthenticated
81
     * @uses $this->sessionData
82
     * @param string $messageName
83
     * @param array $messageOptions
84
     */
85
    protected function prepareForNextMessage($messageName, $messageOptions)
86
    {
87
        if ($this->isAuthenticated === true && is_int($this->sessionData['sequenceNumber'])) {
88
            $this->sessionData['sequenceNumber']++;
89
        }
90
91
        $headers = $this->createSoapHeaders($this->sessionData, $this->params, $messageName, $messageOptions);
92
93
        $this->getSoapClient($messageName)->__setSoapHeaders(null);
94
        $this->getSoapClient($messageName)->__setSoapHeaders($headers);
95
    }
96
97
98
    /**
99
     * Handles post message actions
100
     *
101
     * - look for session info and set status variables
102
     * - checks for message errors?
103
     * - ends terminated sessions
104
     *
105
     * @param string $messageName
106
     * @param string $lastResponse
107
     * @param array $messageOptions
108
     * @param mixed $result
109
     * @return void
110
     */
111
    protected function handlePostMessage($messageName, $lastResponse, $messageOptions, $result)
112
    {
113
        //CHECK FOR SESSION DATA:
114
        if ($this->isStateful() === true) {
115
            //We need to extract session info
116
            $this->sessionData = $this->getSessionDataFromHeader($lastResponse);
117
            $this->isAuthenticated = (!empty($this->sessionData['sessionId']) &&
118
                !empty($this->sessionData['sequenceNumber']) &&
119
                !empty($this->sessionData['securityToken']));
120
        } else {
121
            $this->isAuthenticated = false;
122
        }
123
    }
124
125
    /**
126
     * @param string $responseMsg the full response XML received.
127
     * @return array
128
     */
129
    protected function getSessionDataFromHeader($responseMsg)
130
    {
131
        $newSessionData = [
132
            'sessionId' => null,
133
            'sequenceNumber' => null,
134
            'securityToken' => null
135
        ];
136
137
        $responseDomDoc = new \DOMDocument('1.0', 'UTF-8');
138
        $responseDomDoc->loadXML($responseMsg);
139
        $responseDomXpath = new \DOMXPath($responseDomDoc);
140
        $responseDomXpath->registerNamespace('awsse', 'http://xml.amadeus.com/2010/06/Session_v3');
141
142
        $queryTransactionStatusCode = "string(//awsse:Session/@TransactionStatusCode)";
143
144
        $transactionStatusCode = $responseDomXpath->evaluate($queryTransactionStatusCode);
145
146
        if (mb_strtolower($transactionStatusCode) !== "end") {
147
            $querySessionId = "string(//awsse:Session/awsse:SessionId/text())";
148
            $querySequenceNumber = "string(//awsse:Session/awsse:SequenceNumber/text())";
149
            $querySecurityToken = "string(//awsse:Session/awsse:SecurityToken/text())";
150
151
            $newSessionData['sessionId'] = $responseDomXpath->evaluate($querySessionId);
152
            $newSessionData['sequenceNumber'] = $responseDomXpath->evaluate($querySequenceNumber);
153
            if (!empty($newSessionData['sequenceNumber'])) {
154
                $newSessionData['sequenceNumber'] = (int) $newSessionData['sequenceNumber'];
155
            }
156
            $newSessionData['securityToken'] = $responseDomXpath->evaluate($querySecurityToken);
157
        }
158
159
        unset($responseDomDoc, $responseDomXpath);
160
161
        return $newSessionData;
162
    }
163
164
    /**
165
     * Create the Soap Headers to be used on the subsequent request.
166
     *
167
     * This depends on the current Session Data (if there is an active session) and
168
     * the Session Handler parameters (to create a new or stateless session)
169
     *
170
     * You can also terminate the session with $doEndSession = true
171
     *
172
     * @param array $sessionData
173
     * @param Client\Params\SessionHandlerParams $params
174
     * @param string $messageName
175
     * @param array $messageOptions
176
     * @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...
177
     */
178
    protected function createSoapHeaders($sessionData, $params, $messageName, $messageOptions)
179
    {
180
        $headersToSet = [];
181
182
        $wsdlId = $this->getWsdlIdFor($messageName);
183
        $wsdl = $this->wsdlIds[$wsdlId];
184
185
        //CHECK STATEFUL
186
        $stateful = $this->isStateful();
187
188
        //Message ID header
189
        array_push(
190
            $headersToSet,
191
            new \SoapHeader(
192
                'http://www.w3.org/2005/08/addressing',
193
                'MessageID',
194
                $this->generateGuid()
195
            )
196
        );
197
198
        //Action header
199
        array_push(
200
            $headersToSet,
201
            new \SoapHeader(
202
                'http://www.w3.org/2005/08/addressing',
203
                'Action',
204
                $this->getActionFromWsdl($wsdl, $messageName)
205
            )
206
        );
207
208
        //To header
209
        array_push(
210
            $headersToSet,
211
            new \SoapHeader(
212
                'http://www.w3.org/2005/08/addressing',
213
                'To',
214
                $this->getEndpointFromWsdl($wsdl, $messageName)
215
            )
216
        );
217
218
        //Send authentication info
219
        if ($this->isAuthenticated === false) {
220
            //Generate nonce, msg creation string & password digest:
221
            $password = base64_decode($params->authParams->passwordData);
222
            $creation = new \DateTime('now', new \DateTimeZone('UTC'));
223
            $t = microtime(true);
224
            $micro = sprintf("%03d", ($t - floor($t)) * 1000);
225
            $creationString = $this->createDateTimeStringForAuth($creation, $micro);
226
            $messageNonce = $this->generateUniqueNonce($params->authParams->nonceBase, $creationString);
227
            $encodedNonce = base64_encode($messageNonce);
228
            $digest = $this->generatePasswordDigest($password, $creationString, $messageNonce);
229
230
            $securityHeaderXml = $this->generateSecurityHeaderRawXml(
231
                $params->authParams->userId,
232
                $encodedNonce,
233
                $digest,
234
                $creationString
235
            );
236
237
            //Authentication header
238
            array_push(
239
                $headersToSet,
240
                new \SoapHeader(
241
                    'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wsswssecurity-secext-1.0.xsd',
242
                    'Security',
243
                    new \SoapVar($securityHeaderXml, XSD_ANYXML)
244
                )
245
            );
246
247
            if ($stateful === true) {
248
                //Not authenticated but stateful: start session!
249
                array_push(
250
                    $headersToSet,
251
                    new \SoapHeader(
252
                        'http://xml.amadeus.com/2010/06/Session_v3',
253
                        'Session',
254
                        new Client\Struct\HeaderV4\Session(
255
                            null,
256
                            "Start"
257
                        )
258
                    )
259
                );
260
            }
261
262
            //AMA_SecurityHostedUser header
263
            array_push(
264
                $headersToSet,
265
                new \SoapHeader(
266
                    'http://xml.amadeus.com/2010/06/Security_v1',
267
                    'AMA_SecurityHostedUser',
268
                    new Client\Struct\HeaderV4\SecurityHostedUser(
269
                        $params->authParams->officeId,
270
                        $params->authParams->originatorTypeCode,
271
                        1,
272
                        $params->authParams->dutyCode
273
                    )
274
                )
275
            );
276
        } elseif ($stateful === true) {
277
            //We are authenticated and stateful: provide session header to continue or terminate session
278
            $statusCode =
279
                (isset($messageOptions['endSession']) && $messageOptions['endSession'] === true) ?
280
                    "End" : "InSeries";
281
282
            array_push(
283
                $headersToSet,
284
                new \SoapHeader(
285
                    'http://xml.amadeus.com/2010/06/Session_v3',
286
                    'Session',
287
                    new Client\Struct\HeaderV4\Session(
288
                        $sessionData,
289
                        $statusCode
290
                    )
291
                )
292
            );
293
        }
294
295
        return $headersToSet;
296
    }
297
298
    /**
299
     * Get the Web Services server Endpoint from the WSDL.
300
     *
301
     * @param string $wsdlFilePath
302
     * @param string $messageName
303
     * @return string
304
     */
305
    protected function getEndpointFromWsdl($wsdlFilePath, $messageName)
306
    {
307
        $wsdlId = $this->getWsdlIdFor($messageName);
308
309
        $this->loadWsdlXpath(
310
            $wsdlFilePath,
311
            $wsdlId
312
        );
313
314
        return $this->wsdlDomXpath[$wsdlId]->evaluate(self::XPATH_ENDPOINT);
315
    }
316
317
    /**
318
     * Get the SOAPAction for a given message from the WSDL contents.
319
     *
320
     * @param string $wsdlFilePath
321
     * @param string $messageName
322
     * @return string
323
     */
324
    protected function getActionFromWsdl($wsdlFilePath, $messageName)
325
    {
326
        $wsdlId = $this->getWsdlIdFor($messageName);
327
328
        $this->loadWsdlXpath(
329
            $wsdlFilePath,
330
            $wsdlId
331
        );
332
333
        $action = $this->wsdlDomXpath[$wsdlId]->evaluate(sprintf(self::XPATH_OPERATION_ACTION, $messageName));
334
335
        return $action;
336
    }
337
338
    /**
339
     * Generate a GUID
340
     *
341
     * @return string
342
     */
343
    protected function generateGuid()
344
    {
345
        mt_srand((double) microtime() * 10000);
346
        $charId = strtoupper(md5(uniqid(rand(), true)));
347
        $hyphen = chr(45); // "-"
348
349
        $uuid = substr($charId, 0, 8) . $hyphen
350
            .substr($charId, 8, 4) . $hyphen
351
            .substr($charId, 12, 4) . $hyphen
352
            .substr($charId, 16, 4) . $hyphen
353
            .substr($charId, 20, 12);
354
355
        return $uuid;
356
    }
357
358
    /**
359
     * @param string $originator
360
     * @param string $nonce
361
     * @param string $pwDigest
362
     * @param string $creationTimeString
363
     * @return string
364
     */
365
    protected function generateSecurityHeaderRawXml($originator, $nonce, $pwDigest, $creationTimeString)
366
    {
367
        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...
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 131 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
368
	<oas:UsernameToken xmlns:oas1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" oas1:Id="UsernameToken-1">
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 142 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
369
		<oas:Username>' . $originator . '</oas:Username>
370
		<oas:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">' . $nonce . '</oas:Nonce>
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 149 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
371
		<oas:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">' . $pwDigest . '</oas:Password>
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 153 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
372
		<oas1:Created>' . $creationTimeString . '</oas1:Created>
373
	</oas:UsernameToken>
374
</oas:Security>';
375
    }
376
377
378
    /**
379
     * @param string $nonceBase
380
     * @param string $creationString
381
     * @return string
382
     */
383
    protected function generateUniqueNonce($nonceBase, $creationString)
384
    {
385
        return substr(
386
            sha1(
387
                $nonceBase . $creationString,
388
                true
389
            ),
390
            0,
391
            16
392
        );
393
    }
394
395
    /**
396
     * Generates a Password Digest following this algorithm:
397
     * HashedPassword = Base64(SHA-1( nonce + created + SHA-1 ( password )))
398
     * as defined in
399
     * 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
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 221 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
400
     *
401
     * EXAMPLE: with:
402
     *  Nonce in Base 64 = 'PZgFvh5439plJpKpIyf5ucmXhNU='
403
     *  Timestamp = '2013-01-11T09:41:03Z'
404
     *  Clear Password = 'WBSPassword'
405
     * The digest algorithm returns the Encrypted Password in Base 64:
406
     *  HshPwd = 'ic3AOJElVpvkz9ZBKd105Siry28='
407
     *
408
     * @param string $password CLEARTEXT password (NOT the base64 encoded password used in Security_Authenticate)
409
     * @param string $creationString message creation datetime
410
     *                               UTC Format: yyyy-mm-ddTHH:MM:SSZ or yyyy-mm-ddTHH:MM:SS.sssZ
411
     * @param string $messageNonce Random unique string
412
     * @return string The generated Password Digest
413
     */
414
    protected function generatePasswordDigest($password, $creationString, $messageNonce)
415
    {
416
        return base64_encode(sha1($messageNonce . $creationString . sha1($password, true), true));
417
    }
418
419
    /**
420
     * @param \DateTime $creationDateTime
421
     * @param string $micro
422
     * @return string
423
     */
424
    protected function createDateTimeStringForAuth($creationDateTime, $micro)
425
    {
426
        $creationDateTime->setTimezone(new \DateTimeZone('UTC'));
427
        return $creationDateTime->format("Y-m-d\TH:i:s:") . $micro . 'Z';
428
    }
429
430
    /**
431
     * Make SoapClient options for Soap Header 4 handler
432
     *
433
     * @return array
434
     */
435 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...
436
    {
437
        $options = $this->soapClientOptions;
438
        $options['classmap'] = array_merge(Classmap::$soapheader4map, Classmap::$map);
439
440
        if (!empty($this->params->soapClientOptions)) {
441
            $options = array_merge($options, $this->params->soapClientOptions);
442
        }
443
444
        return $options;
445
    }
446
}
447