Completed
Pull Request — master (#66)
by marijn
05:49
created

NTLMSoapClient::getResponseCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 2
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
    );
92
93
    /**
94
     * @TODO: Make this smarter. It should know and search what headers to remove on what actions
95
     *
96
     * @param string $name
97
     * @param string $args
98
     * @return mixed
99
     */
100
    public function __call($name, $args)
101
    {
102
        $this->__setSoapHeaders(null);
103
104
        $headers = array(
105
            $this->ewsHeaders['version'],
106
            $this->ewsHeaders['impersonation'],
107
        );
108
109
        if (!in_array($name, $this->callsWithoutTimezone)) {
110
            $headers[] = $this->ewsHeaders['timezone'];
111
        }
112
113
        $headers = array_filter($headers, function ($header) {
114
            if (!($header instanceof SoapHeader)) {
115
                return false;
116
            }
117
118
            return true;
119
        });
120
121
        $this->__setSoapHeaders($headers);
122
        return parent::__call($name, $args);
123
    }
124
125
    /**
126
     * @param string $location
127
     * @param string $wsdl
128
     * @param array $options
129
     */
130 36
    public function __construct($location, $auth, $wsdl, $options = array())
131
    {
132 36
        $this->auth = $auth;
133
134 36
        $options = array_replace_recursive([
135
            'httpPlayback' => [
136
                'mode' => null
137 36
            ]
138 36
        ], $options);
139
140 36
        $options['location'] = $location;
141
142
        // If a version was set then add it to the headers.
143 36
        if (!empty($options['version'])) {
144 35
            $this->ewsHeaders['version'] = new SoapHeader(
145 35
                'http://schemas.microsoft.com/exchange/services/2006/types',
146 35
                'RequestServerVersion Version="'.$options['version'].'"'
147 35
            );
148 35
        }
149
150
        // If impersonation was set then add it to the headers.
151 36
        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
        }
163
164 36
        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 36
        $this->httpClient = Factory::getInstance($options['httpPlayback']);
177
178 36
        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
    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
        );
206
207
        $postOptions = array_replace_recursive($postOptions, $this->auth);
208
209
        $response = $this->httpClient->post($location, $postOptions);
210
211
        $this->__last_request_headers = $postOptions['headers'];
212
        $this->_responseCode = $response->getStatusCode();
213
214
        return $response->getBody()->__toString();
215
    }
216
217
    /**
218
     * Returns last SOAP request headers
219
     *
220
     * @link http://php.net/manual/en/function.soap-soapclient-getlastrequestheaders.php
221
     *
222
     * @return string the last soap request headers
223
     */
224
    public function __getLastRequestHeaders()
225
    {
226
        return implode('n', $this->__last_request_headers)."\n";
227
    }
228
229
    /**
230
     * Set validation certificate
231
     *
232
     * @param bool $validate
233
     * @return $this
234
     */
235
    public function validateCertificate($validate = true)
236
    {
237
        $this->validate = $validate;
238
239
        return $this;
240
    }
241
242
    /**
243
     * Returns the response code from the last request
244
     *
245
     * @return integer
246
     */
247
    public function getResponseCode()
248
    {
249
        return $this->_responseCode;
250
    }
251
}
252