Completed
Push — master ( 64f90f...31bc6f )
by Gareth
03:13
created

NTLMSoapClient   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 212
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 78.46%

Importance

Changes 13
Bugs 1 Features 0
Metric Value
wmc 13
c 13
b 1
f 0
lcom 1
cbo 1
dl 0
loc 212
ccs 51
cts 65
cp 0.7846
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __call() 0 21 4
B __construct() 0 50 5
B __doRequest() 0 24 1
A __getLastRequestHeaders() 0 4 1
A validateCertificate() 0 6 1
A getResponseCode() 0 4 1
1
<?php
2
3
namespace jamesiarmes\PEWS\API;
4
5
use jamesiarmes\PEWS\API\Type\ExchangeImpersonation;
6
use SoapClient;
7
use GuzzleHttp;
8
use SoapHeader;
9
use jamesiarmes\PEWS\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 18
     * @var array
78
     */
79 18
    protected $ewsHeaders = array(
80
        'version' => null,
81
        'impersonation' => null,
82
        'timezone' => null
83
    );
84 1
85
    protected $auth;
86
87 1
    protected $callsWithoutTimezone = array(
88
        'DeleteItem',
89 18
        '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 27
     * @return mixed
100
     */
101 27
    public function __call($name, $args)
102
    {
103 27
        //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
        if (isset($args[0]) && $args[0] instanceof Type) {
106 27
            $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 27
        }
108
109 27
        $this->__default_headers = array (
110
            $this->ewsHeaders['version'],
111
            $this->ewsHeaders['impersonation']
112 27
        );
113 26
114 26
        if (!in_array($name, $this->callsWithoutTimezone)) {
115 26
            $this->__default_headers[] = $this->ewsHeaders['timezone'];
116 26
        }
117 26
118
        $response = parent::__call($name, $args);
119
        $this->__default_headers = [];
120 27
        return $response;
121 2
    }
122 2
123 2
    /**
124 2
     * @param mixed $location
125
     * @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...
126 2
     * @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...
127 2
     * @param $wsdl
128 2
     * @param array $options
129 2
     */
130 2
    public function __construct($location, $auth, $wsdl, $options = array())
131 2
    {
132
        $this->auth = $auth;
133 27
134
        $options = array_replace_recursive([
135
            'httpPlayback' => [
136
                'mode' => null
137
            ]
138
        ], $options);
139
140
        $options['location'] = $location;
141
142
        // If a version was set then add it to the headers.
143
        if (!empty($options['version'])) {
144
            $this->ewsHeaders['version'] = new SoapHeader(
145 27
                'http://schemas.microsoft.com/exchange/services/2006/types',
146
                'RequestServerVersion Version="' . $options['version'] . '"'
147 27
            );
148 27
        }
149
150
        // If impersonation was set then add it to the headers.
151
        if (!empty($options['impersonation'])) {
152
            $impersonation = $options['impersonation'];
153
            if (is_string($impersonation)) {
154
                $impersonation = ExchangeImpersonation::fromEmailAddress($options['impersonation']);
155
            }
156
157
            $this->ewsHeaders['impersonation'] = new SoapHeader(
158
                'http://schemas.microsoft.com/exchange/services/2006/types',
159
                'ExchangeImpersonation',
160
                $impersonation->toXmlObject()
161
            );
162 18
        }
163
164
        if (!empty($options['timezone'])) {
165 18
            $this->ewsHeaders['timezone'] = new SoapHeader(
166
                'http://schemas.microsoft.com/exchange/services/2006/types',
167 18
                'TimeZoneContext',
168 18
                array(
169 18
                    'TimeZoneDefinition' => array(
170
                        'Id' => $options['timezone']
171 18
                    )
172 18
                )
173
            );
174 18
        }
175
176 18
        $this->httpPlayback = HttpPlayback::getInstance($options['httpPlayback']);
177
178 18
        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: jamesiarmes\PEWS\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...
179 18
    }
180
181 18
    /**
182 18
     * Performs a SOAP request
183
     *
184 18
     * @link http://php.net/manual/en/function.soap-soapclient-dorequest.php
185
     *
186
     * @param string $request the xml soap request
187
     * @param string $location the url to request
188
     * @param string $action the soap action.
189
     * @param integer $version the soap version
190
     * @param integer $one_way
191
     * @return string the xml soap response.
192
     */
193
    public function __doRequest($request, $location, $action, $version, $one_way = 0)
194
    {
195
        $postOptions = array(
196
            'body' => $request,
197
            'headers' => array(
198
                'Connection' => 'Keep-Alive',
199
                'User-Agent' => 'PHP-SOAP-CURL',
200
                'Content-Type' => 'text/xml; charset=utf-8',
201
                'SOAPAction' => $action
202
            ),
203
            'verify' => $this->validate,
204
            'http_errors' => false
205 1
        );
206
207 1
        $postOptions = array_replace_recursive($postOptions, $this->auth);
208
209 1
        $client = $this->httpPlayback->getHttpClient();
210
        $response = $client->post($location, $postOptions);
211
212
        $this->__last_request_headers = $postOptions['headers'];
213
        $this->_responseCode = $response->getStatusCode();
214
215
        return $response->getBody()->__toString();
216
    }
217 18
218
    /**
219 18
     * Returns last SOAP request headers
220
     *
221
     * @link http://php.net/manual/en/function.soap-soapclient-getlastrequestheaders.php
222
     *
223
     * @return string the last soap request headers
224
     */
225
    public function __getLastRequestHeaders()
226
    {
227
        return implode('n', $this->__last_request_headers) . "\n";
228
    }
229
230
    /**
231
     * Set validation certificate
232
     *
233
     * @param bool $validate
234
     * @return $this
235
     */
236
    public function validateCertificate($validate = true)
237
    {
238
        $this->validate = $validate;
239
240
        return $this;
241
    }
242
243
    /**
244
     * Returns the response code from the last request
245
     *
246
     * @return integer
247
     */
248
    public function getResponseCode()
249
    {
250
        return $this->_responseCode;
251
    }
252
}
253