Completed
Push — master ( d7c914...6c6572 )
by Gareth
11:08 queued 07:24
created

NTLMSoapClient::__construct()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 50
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 5.1158
Metric Value
dl 0
loc 50
ccs 20
cts 24
cp 0.8333
rs 8.6315
cc 5
eloc 28
nc 12
nop 4
crap 5.1158
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 22
        $this->__default_headers = array (
110 22
            $this->ewsHeaders['version'],
111 22
            $this->ewsHeaders['impersonation']
112
        );
113
114 22
        if (!in_array($name, $this->callsWithoutTimezone)) {
115 22
            $this->__default_headers[] = $this->ewsHeaders['timezone'];
116
        }
117
118 22
        $response = parent::__call($name, $args);
119 22
        $this->__default_headers = [];
120 22
        return $response;
121
    }
122
123
    /**
124
     * @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
     * @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
     * @param $wsdl
128
     * @param array $options
129
     */
130 29
    public function __construct($location, $auth, $wsdl, $options = array())
131
    {
132 29
        $this->auth = $auth;
133
134 29
        $options = array_replace_recursive([
135
            'httpPlayback' => [
136
                'mode' => null
137
            ]
138 29
        ], $options);
139
140 29
        $options['location'] = $location;
141
142
        // If a version was set then add it to the headers.
143 29
        if (!empty($options['version'])) {
144 28
            $this->ewsHeaders['version'] = new SoapHeader(
145 28
                'http://schemas.microsoft.com/exchange/services/2006/types',
146 28
                'RequestServerVersion Version="' . $options['version'] . '"'
147
            );
148
        }
149
150
        // If impersonation was set then add it to the headers.
151 29
        if (!empty($options['impersonation'])) {
152 1
            $impersonation = $options['impersonation'];
153 1
            if (is_string($impersonation)) {
154 1
                $impersonation = ExchangeImpersonation::fromEmailAddress($options['impersonation']);
155
            }
156
157 1
            $this->ewsHeaders['impersonation'] = new SoapHeader(
158 1
                'http://schemas.microsoft.com/exchange/services/2006/types',
159 1
                'ExchangeImpersonation',
160 1
                $impersonation->toXmlObject()
161
            );
162
        }
163
164 29
        if (!empty($options['timezone'])) {
165
            $this->ewsHeaders['timezone'] = new SoapHeader(
166
                'http://schemas.microsoft.com/exchange/services/2006/types',
167
                'TimeZoneContext',
168
                array(
169
                    'TimeZoneDefinition' => array(
170
                        'Id' => $options['timezone']
171
                    )
172
                )
173
            );
174
        }
175
176 29
        $this->httpPlayback = HttpPlayback::getInstance($options['httpPlayback']);
177
178 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...
179
    }
180
181
    /**
182
     * Performs a SOAP request
183
     *
184
     * @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 22
    public function __doRequest($request, $location, $action, $version, $one_way = 0)
194
    {
195
        $postOptions = array(
196 22
            'body' => $request,
197
            'headers' => array(
198 22
                'Connection' => 'Keep-Alive',
199 22
                'User-Agent' => 'PHP-SOAP-CURL',
200 22
                'Content-Type' => 'text/xml; charset=utf-8',
201 22
                'SOAPAction' => $action
202
            ),
203 22
            'verify' => $this->validate,
204
            'http_errors' => false
205
        );
206
207 22
        $postOptions = array_replace_recursive($postOptions, $this->auth);
208
209 22
        $client = $this->httpPlayback->getHttpClient();
210 22
        $response = $client->post($location, $postOptions);
211
212 22
        $this->__last_request_headers = $postOptions['headers'];
213 22
        $this->_responseCode = $response->getStatusCode();
214
215 22
        return $response->getBody()->__toString();
216
    }
217
218
    /**
219
     * 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 1
    public function validateCertificate($validate = true)
237
    {
238 1
        $this->validate = $validate;
239
240 1
        return $this;
241
    }
242
243
    /**
244
     * Returns the response code from the last request
245
     *
246
     * @return integer
247
     */
248 22
    public function getResponseCode()
249
    {
250 22
        return $this->_responseCode;
251
    }
252
}
253