NTLMSoapClient   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 225
Duplicated Lines 0 %

Test Coverage

Coverage 85.54%

Importance

Changes 8
Bugs 2 Features 0
Metric Value
eloc 88
c 8
b 2
f 0
dl 0
loc 225
ccs 71
cts 83
cp 0.8554
rs 10
wmc 13

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getResponseCode() 0 3 1
A validateCertificate() 0 5 1
A __getLastRequestHeaders() 0 4 1
A __doRequest() 0 38 3
A __call() 0 20 2
A __construct() 0 49 5
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
    #[\ReturnTypeWillChange]
102
    public function __call($name, $args)
103
    {
104 30
        $this->__setSoapHeaders(null);
105
106 30
        $headers = array(
107 30
            $this->ewsHeaders['version'],
108 30
            $this->ewsHeaders['impersonation'],
109 30
        );
110
111 30
        if (!in_array($name, $this->callsWithoutTimezone)) {
112 30
            $headers[] = $this->ewsHeaders['timezone'];
113
        }
114
115 30
        $headers = array_filter($headers, function ($header) {
116 30
            return $header instanceof SoapHeader;
117 30
        });
118
119 30
        $this->__setSoapHeaders($headers);
120 30
        return parent::__call($name, $args);
0 ignored issues
show
Bug introduced by
$args of type string is incompatible with the type array expected by parameter $args of SoapClient::__call(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

120
        return parent::__call($name, /** @scrutinizer ignore-type */ $args);
Loading history...
121
    }
122
123
    /**
124
     * @param string $location
125
     * @param string $wsdl
126
     * @param array $options
127
     */
128 38
    public function __construct($location, $auth, $wsdl, $options = array())
129
    {
130 38
        $this->auth = $auth;
131
132 38
        $options = array_replace_recursive([
133 38
            'httpPlayback' => [
134 38
                'mode' => null
135 38
            ]
136 38
        ], $options);
137
138 38
        $options['location'] = $location;
139
140
        // If a version was set then add it to the headers.
141 38
        if (!empty($options['version'])) {
142 37
            $this->ewsHeaders['version'] = new SoapHeader(
143 37
                'http://schemas.microsoft.com/exchange/services/2006/types',
144 37
                'RequestServerVersion Version="'.$options['version'].'"'
145 37
            );
146
        }
147
148
        // If impersonation was set then add it to the headers.
149 38
        if (!empty($options['impersonation'])) {
150 1
            $impersonation = $options['impersonation'];
151 1
            if (is_string($impersonation)) {
152 1
                $impersonation = ExchangeImpersonation::fromEmailAddress($options['impersonation']);
153
            }
154
155 1
            $this->ewsHeaders['impersonation'] = new SoapHeader(
156 1
                'http://schemas.microsoft.com/exchange/services/2006/types',
157 1
                'ExchangeImpersonation',
158 1
                $impersonation->toXmlObject()
159 1
            );
160
        }
161
162 38
        if (!empty($options['timezone'])) {
163
            $this->ewsHeaders['timezone'] = new SoapHeader(
164
                'http://schemas.microsoft.com/exchange/services/2006/types',
165
                'TimeZoneContext',
166
                array(
167
                    'TimeZoneDefinition' => array(
168
                        'Id' => $options['timezone']
169
                    )
170
                )
171
            );
172
        }
173
174 38
        $this->httpClient = Factory::getInstance($options['httpPlayback']);
175
176 38
        parent::__construct($wsdl, $options);
177
    }
178
179
    /**
180
     * Performs a SOAP request
181
     *
182
     * @link http://php.net/manual/en/function.soap-soapclient-dorequest.php
183
     *
184
     * @param string $request the xml soap request
185
     * @param string $location the url to request
186
     * @param string $action the soap action.
187
     * @param integer $version the soap version
188
     * @param integer $one_way
189
     * @return string the xml soap response.
190
     */
191 30
    #[\ReturnTypeWillChange]
192
    public function __doRequest($request, $location, $action, $version, $one_way = 0)
193
    {
194 30
        $postOptions = array(
195 30
            'body' => $request,
196 30
            'headers' => array(
197 30
                'Connection' => 'Keep-Alive',
198 30
                'User-Agent' => 'PHP-SOAP-CURL',
199 30
                'Content-Type' => 'text/xml; charset=utf-8',
200 30
                'SOAPAction' => $action
201 30
            ),
202 30
            'verify' => $this->validate,
203 30
            'http_errors' => false
204 30
        );
205
206 30
        $postOptions = array_replace_recursive($postOptions, $this->auth);
207
208 30
        $response = $this->httpClient->post($location, $postOptions);
209
210 30
        $this->__last_request_headers = $postOptions['headers'];
211 30
        $this->_responseCode = $response->getStatusCode();
212
213 30
        $responseStr = $response->getBody()->__toString();
214 30
        if ($this->_responseCode < 200 || $this->_responseCode >= 300) {
215
            return $responseStr;
216
        }
217
218 30
        libxml_use_internal_errors(true);
219 30
        $dom = new \DOMDocument("1.0", "UTF-8");
220 30
        $dom->strictErrorChecking = false;
221 30
        $dom->validateOnParse = false;
222 30
        $dom->recover = true;
223 30
        $dom->loadXML($responseStr);
224 30
        $xml = simplexml_import_dom($dom);
225 30
        libxml_clear_errors();
226 30
        libxml_use_internal_errors(false);
227
228 30
        return $xml->asXML();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $xml->asXML() also could return the type true which is incompatible with the documented return type string.
Loading history...
229
    }
230
231
    /**
232
     * Returns last SOAP request headers
233
     *
234
     * @link http://php.net/manual/en/function.soap-soapclient-getlastrequestheaders.php
235
     *
236
     * @return string the last soap request headers
237
     */
238
    #[\ReturnTypeWillChange]
239
    public function __getLastRequestHeaders()
240
    {
241
        return implode('n', $this->__last_request_headers)."\n";
242
    }
243
244
    /**
245
     * Set validation certificate
246
     *
247
     * @param bool $validate
248
     * @return $this
249
     */
250 1
    public function validateCertificate($validate = true)
251
    {
252 1
        $this->validate = $validate;
253
254 1
        return $this;
255
    }
256
257
    /**
258
     * Returns the response code from the last request
259
     *
260
     * @return integer
261
     */
262 30
    public function getResponseCode()
263
    {
264 30
        return $this->_responseCode;
265
    }
266
}
267