Completed
Pull Request — master (#201)
by Edd
01:58
created

NTLMSoapClient::__doRequest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 5
dl 0
loc 34
ccs 23
cts 23
cp 1
crap 1
rs 9.376
c 0
b 0
f 0
1
<?php
2
3
namespace garethp\ews\API;
4
5
use garethp\ews\API\Type\ExchangeImpersonation;
6
use SoapClient;
7
use SoapHeader;
8
use garethp\HttpPlayback\Factory;
9
10
/**
11
 * Contains NTLMSoapClient.
12
 */
13
14
/**
15
 * Soap Client using Microsoft's NTLM Authentication.
16
 *
17
 * Copyright (c) 2008 Invest-In-France Agency http://www.invest-in-france.org
18
 *
19
 * Author : Thomas Rabaix
20
 *
21
 * Permission to use, copy, modify, and distribute this software for any
22
 * purpose with or without fee is hereby granted, provided that the above
23
 * copyright notice and this permission notice appear in all copies.
24
 *
25
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
26
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
27
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
28
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
29
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
30
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
31
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
32
 *
33
 * @link http://rabaix.net/en/articles/2008/03/13/using-soap-php-with-ntlm-authentication
34
 * @author Thomas Rabaix
35
 *
36
 * @package php-ews\Auth
37
 *
38
 * @property array __default_headers
39
 */
40
class NTLMSoapClient extends SoapClient
41
{
42
    /**
43
     * Username for authentication on the exchnage server
44
     *
45
     * @var string
46
     */
47
    protected $user;
48
49
    /**
50
     * Password for authentication on the exchnage server
51
     *
52
     * @var string
53
     */
54
    protected $password;
55
56
    /**
57
     * Whether or not to validate ssl certificates
58
     *
59
     * @var boolean
60
     */
61
    protected $validate = false;
62
63
    private $httpClient;
64
65
    protected $__last_request_headers;
66
67
    protected $_responseCode;
68
69
    /**
70
     * An array of headers for us to store or use. Since not all requests use all headers (DeleteItem and SyncItems
71
     * don't allow you to pass a Timezone for example), we need to be able to smartly decide what headers to include
72
     * and exclude from a request. Until we have propper selection (an array of all known operations and what headers
73
     * are allowed for example), this seems like a decent solution for storing the headers before we decide if they
74
     * belong in the request or not)
75
     *
76
     * @var array
77
     */
78
    protected $ewsHeaders = array(
79
        'version' => null,
80
        'impersonation' => null,
81
        'timezone' => null
82
    );
83
84
    protected $auth;
85
86
    protected $callsWithoutTimezone = array(
87
        'DeleteItem',
88
        'SyncFolderItems',
89
        'GetServerTimeZones',
90
        'ConvertId',
91
        'MoveItem'
92
    );
93
94
    /**
95
     * @TODO: Make this smarter. It should know and search what headers to remove on what actions
96
     *
97
     * @param string $name
98
     * @param string $args
99
     * @return mixed
100
     */
101 30
    public function __call($name, $args)
102
    {
103 30
        $this->__setSoapHeaders(null);
104
105
        $headers = array(
106 30
            $this->ewsHeaders['version'],
107 30
            $this->ewsHeaders['impersonation'],
108 30
        );
109
110 30
        if (!in_array($name, $this->callsWithoutTimezone)) {
111 30
            $headers[] = $this->ewsHeaders['timezone'];
112 30
        }
113
114 30
        $headers = array_filter($headers, function ($header) {
115 30
            return $header instanceof SoapHeader;
116 30
        });
117
118 30
        $this->__setSoapHeaders($headers);
119 30
        return parent::__call($name, $args);
120
    }
121
122
    /**
123
     * @param string $location
124
     * @param string $wsdl
125
     * @param array $options
126
     */
127 38
    public function __construct($location, $auth, $wsdl, $options = array())
128
    {
129 38
        $this->auth = $auth;
130
131 38
        $options = array_replace_recursive([
132
            'httpPlayback' => [
133
                'mode' => null
134 38
            ]
135 38
        ], $options);
136
137 38
        $options['location'] = $location;
138
139
        // If a version was set then add it to the headers.
140 38
        if (!empty($options['version'])) {
141 37
            $this->ewsHeaders['version'] = new SoapHeader(
142 37
                'http://schemas.microsoft.com/exchange/services/2006/types',
143 37
                'RequestServerVersion Version="'.$options['version'].'"'
144 37
            );
145 37
        }
146
147
        // If impersonation was set then add it to the headers.
148 38
        if (!empty($options['impersonation'])) {
149 1
            $impersonation = $options['impersonation'];
150 1
            if (is_string($impersonation)) {
151 1
                $impersonation = ExchangeImpersonation::fromEmailAddress($options['impersonation']);
152 1
            }
153
154 1
            $this->ewsHeaders['impersonation'] = new SoapHeader(
155 1
                'http://schemas.microsoft.com/exchange/services/2006/types',
156 1
                'ExchangeImpersonation',
157 1
                $impersonation->toXmlObject()
158 1
            );
159 1
        }
160
161 38
        if (!empty($options['timezone'])) {
162
            $this->ewsHeaders['timezone'] = new SoapHeader(
163
                'http://schemas.microsoft.com/exchange/services/2006/types',
164
                'TimeZoneContext',
165
                array(
166
                    'TimeZoneDefinition' => array(
167
                        'Id' => $options['timezone']
168
                    )
169
                )
170
            );
171
        }
172
173 38
        $this->httpClient = Factory::getInstance($options['httpPlayback']);
174
175 38
        parent::__construct($wsdl, $options);
176 38
    }
177
178
    /**
179
     * Performs a SOAP request
180
     *
181
     * @link http://php.net/manual/en/function.soap-soapclient-dorequest.php
182
     *
183
     * @param string $request the xml soap request
184
     * @param string $location the url to request
185
     * @param string $action the soap action.
186
     * @param integer $version the soap version
187
     * @param integer $one_way
188
     * @return string the xml soap response.
189
     */
190 30
    public function __doRequest($request, $location, $action, $version, $one_way = 0)
191
    {
192
        $postOptions = array(
193 30
            'body' => $request,
194
            'headers' => array(
195 30
                'Connection' => 'Keep-Alive',
196 30
                'User-Agent' => 'PHP-SOAP-CURL',
197 30
                'Content-Type' => 'text/xml; charset=utf-8',
198
                'SOAPAction' => $action
199 30
            ),
200 30
            'verify' => $this->validate,
201
            'http_errors' => false
202 30
        );
203
204 30
        $postOptions = array_replace_recursive($postOptions, $this->auth);
205
206 30
        $response = $this->httpClient->post($location, $postOptions);
207
208 30
        $this->__last_request_headers = $postOptions['headers'];
209 30
        $this->_responseCode = $response->getStatusCode();
210
211 30
        $responseStr = $response->getBody()->__toString();
212 30
        libxml_use_internal_errors(true);
213 30
        $dom = new \DOMDocument("1.0", "UTF-8");
214 30
        $dom->strictErrorChecking = false;
215 30
        $dom->validateOnParse = false;
216 30
        $dom->recover = true;
217 30
        $dom->loadXML($responseStr);
218 30
        $xml = simplexml_import_dom($dom);
219 30
        libxml_clear_errors();
220 30
        libxml_use_internal_errors(false);
221
222 30
        return $xml->asXML();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $xml->asXML(); of type string|false adds false to the return on line 222 which is incompatible with the return type of the parent method SoapClient::__doRequest of type string. It seems like you forgot to handle an error condition.
Loading history...
223
    }
224
225
    /**
226
     * Returns last SOAP request headers
227
     *
228
     * @link http://php.net/manual/en/function.soap-soapclient-getlastrequestheaders.php
229
     *
230
     * @return string the last soap request headers
231
     */
232
    public function __getLastRequestHeaders()
233
    {
234
        return implode('n', $this->__last_request_headers)."\n";
235
    }
236
237
    /**
238
     * Set validation certificate
239
     *
240
     * @param bool $validate
241
     * @return $this
242
     */
243 1
    public function validateCertificate($validate = true)
244
    {
245 1
        $this->validate = $validate;
246
247 1
        return $this;
248
    }
249
250
    /**
251
     * Returns the response code from the last request
252
     *
253
     * @return integer
254
     */
255 30
    public function getResponseCode()
256
    {
257 30
        return $this->_responseCode;
258
    }
259
}
260