Completed
Branch master (09022f)
by Gareth
05:56 queued 03:06
created

NTLMSoapClient::__doRequest()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 1
Metric Value
dl 0
loc 24
ccs 13
cts 13
cp 1
rs 8.9713
cc 1
eloc 16
nc 1
nop 5
crap 1
1
<?php
2
3
namespace garethp\ews\API;
4
5
use garethp\ews\API\Type\ExchangeImpersonation;
6
use SoapClient;
7
use GuzzleHttp;
8
use SoapHeader;
9
use garethp\ews\HttpPlayback\HttpPlayback;
10
11
/**
12
 * Contains NTLMSoapClient.
13
 */
14
15
/**
16
 * Soap Client using Microsoft's NTLM Authentication.
17
 *
18
 * Copyright (c) 2008 Invest-In-France Agency http://www.invest-in-france.org
19
 *
20
 * Author : Thomas Rabaix
21
 *
22
 * Permission to use, copy, modify, and distribute this software for any
23
 * purpose with or without fee is hereby granted, provided that the above
24
 * copyright notice and this permission notice appear in all copies.
25
 *
26
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
27
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
28
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
29
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
30
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
31
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
32
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
33
 *
34
 * @link http://rabaix.net/en/articles/2008/03/13/using-soap-php-with-ntlm-authentication
35
 * @author Thomas Rabaix
36
 *
37
 * @package php-ews\Auth
38
 *
39
 * @property array __default_headers
40
 */
41
class NTLMSoapClient extends SoapClient
42
{
43
    /**
44
     * Username for authentication on the exchnage server
45
     *
46
     * @var string
47
     */
48
    protected $user;
49
50
    /**
51
     * Password for authentication on the exchnage server
52
     *
53
     * @var string
54
     */
55
    protected $password;
56
57
    /**
58
     * Whether or not to validate ssl certificates
59
     *
60
     * @var boolean
61
     */
62
    protected $validate = false;
63
64
    private $httpPlayback;
65
66
    protected $__last_request_headers;
67
68
    protected $_responseCode;
69
70
    /**
71
     * An array of headers for us to store or use. Since not all requests use all headers (DeleteItem and SyncItems
72
     * don't allow you to pass a Timezone for example), we need to be able to smartly decide what headers to include
73
     * and exclude from a request. Until we have propper selection (an array of all known operations and what headers
74
     * are allowed for example), this seems like a decent solution for storing the headers before we decide if they
75
     * belong in the request or not)
76
     *
77
     * @var array
78
     */
79
    protected $ewsHeaders = array(
80
        'version' => null,
81
        'impersonation' => null,
82
        'timezone' => null
83
    );
84
85
    protected $auth;
86
87
    protected $callsWithoutTimezone = array(
88
        'DeleteItem',
89
        'SyncFolderItems',
90
        'GetServerTimeZones',
91
        'ConvertId'
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 22
    public function __call($name, $args)
102
    {
103
        //If the request passed in is our custom type, let's use the toXmlObject function, since that's what it's built
104
        //for
105 22
        if (isset($args[0]) && $args[0] instanceof Type) {
106 22
            $args[0] = $args[0]->toXmlObject();
0 ignored issues
show
Bug introduced by
The method toXmlObject cannot be called on $args[0] (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
107
        }
108
109
        $headers = array (
110 22
            $this->ewsHeaders['version'],
111 22
            $this->ewsHeaders['impersonation'],
112
        );
113
114 22
        if (!in_array($name, $this->callsWithoutTimezone)) {
115 22
            $headers[] = $this->ewsHeaders['timezone'];
116
        }
117
118 22
        $headers = array_filter($headers, function ($header) {
119 22
            if (!($header instanceof SoapHeader)) {
120 22
                return false;
121
            }
122
123 22
            return true;
124 22
        });
125
126 22
        $this->__setSoapHeaders($headers);
127 22
        return parent::__call($name, $args);
128
    }
129
130
    /**
131
     * @param mixed $location
132
     * @param string $user
0 ignored issues
show
Bug introduced by
There is no parameter named $user. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
133
     * @param string $password
0 ignored issues
show
Bug introduced by
There is no parameter named $password. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
134
     * @param $wsdl
135
     * @param array $options
136
     */
137 29
    public function __construct($location, $auth, $wsdl, $options = array())
138
    {
139 29
        $this->auth = $auth;
140
141 29
        $options = array_replace_recursive([
142
            'httpPlayback' => [
143
                'mode' => null
144
            ]
145 29
        ], $options);
146
147 29
        $options['location'] = $location;
148
149
        // If a version was set then add it to the headers.
150 29
        if (!empty($options['version'])) {
151 28
            $this->ewsHeaders['version'] = new SoapHeader(
152 28
                'http://schemas.microsoft.com/exchange/services/2006/types',
153 28
                'RequestServerVersion Version="' . $options['version'] . '"'
154
            );
155
        }
156
157
        // If impersonation was set then add it to the headers.
158 29
        if (!empty($options['impersonation'])) {
159 1
            $impersonation = $options['impersonation'];
160 1
            if (is_string($impersonation)) {
161 1
                $impersonation = ExchangeImpersonation::fromEmailAddress($options['impersonation']);
162
            }
163
164 1
            $this->ewsHeaders['impersonation'] = new SoapHeader(
165 1
                'http://schemas.microsoft.com/exchange/services/2006/types',
166 1
                'ExchangeImpersonation',
167 1
                $impersonation->toXmlObject()
168
            );
169
        }
170
171 29
        if (!empty($options['timezone'])) {
172
            $this->ewsHeaders['timezone'] = new SoapHeader(
173
                'http://schemas.microsoft.com/exchange/services/2006/types',
174
                'TimeZoneContext',
175
                array(
176
                    'TimeZoneDefinition' => array(
177
                        'Id' => $options['timezone']
178
                    )
179
                )
180
            );
181
        }
182
183 29
        $this->httpPlayback = HttpPlayback::getInstance($options['httpPlayback']);
184
185 29
        parent::__construct($wsdl, $options);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SoapClient as the method __construct() does only exist in the following sub-classes of SoapClient: garethp\ews\API\NTLMSoapClient. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
186
    }
187
188
    /**
189
     * Performs a SOAP request
190
     *
191
     * @link http://php.net/manual/en/function.soap-soapclient-dorequest.php
192
     *
193
     * @param string $request the xml soap request
194
     * @param string $location the url to request
195
     * @param string $action the soap action.
196
     * @param integer $version the soap version
197
     * @param integer $one_way
198
     * @return string the xml soap response.
199
     */
200 22
    public function __doRequest($request, $location, $action, $version, $one_way = 0)
201
    {
202
        $postOptions = array(
203 22
            'body' => $request,
204
            'headers' => array(
205 22
                'Connection' => 'Keep-Alive',
206 22
                'User-Agent' => 'PHP-SOAP-CURL',
207 22
                'Content-Type' => 'text/xml; charset=utf-8',
208 22
                'SOAPAction' => $action
209
            ),
210 22
            'verify' => $this->validate,
211
            'http_errors' => false
212
        );
213
214 22
        $postOptions = array_replace_recursive($postOptions, $this->auth);
215
216 22
        $client = $this->httpPlayback->getHttpClient();
217 22
        $response = $client->post($location, $postOptions);
218
219 22
        $this->__last_request_headers = $postOptions['headers'];
220 22
        $this->_responseCode = $response->getStatusCode();
221
222 22
        return $response->getBody()->__toString();
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 22
    public function getResponseCode()
256
    {
257 22
        return $this->_responseCode;
258
    }
259
}
260