Completed
Push — master ( 7f67d6...f80470 )
by Gareth
03:46
created

NTLMSoapClient::__construct()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 50
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 5.2986

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 50
ccs 27
cts 35
cp 0.7714
rs 8.6315
cc 5
eloc 28
nc 12
nop 4
crap 5.2986
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\ews\HttpPlayback\HttpPlayback;
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 $httpPlayback;
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 24
    public function __call($name, $args)
101
    {
102 24
        $this->__setSoapHeaders(null);
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 24
        if (isset($args[0]) && $args[0] instanceof Type) {
106 24
            $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 24
        }
108
109
        $headers = array(
110 24
            $this->ewsHeaders['version'],
111 24
            $this->ewsHeaders['impersonation'],
112 24
        );
113
114 24
        if (!in_array($name, $this->callsWithoutTimezone)) {
115 24
            $headers[] = $this->ewsHeaders['timezone'];
116 24
        }
117
118 24
        $headers = array_filter($headers, function ($header) {
119 24
            if (!($header instanceof SoapHeader)) {
120 24
                return false;
121
            }
122
123 24
            return true;
124 24
        });
125
126 24
        $this->__setSoapHeaders($headers);
127 24
        return parent::__call($name, $args);
128
    }
129
130
    /**
131
     * @param string $location
132
     * @param string $wsdl
133
     * @param array $options
134
     */
135 32
    public function __construct($location, $auth, $wsdl, $options = array())
136
    {
137 32
        $this->auth = $auth;
138
139 32
        $options = array_replace_recursive([
140
            'httpPlayback' => [
141
                'mode' => null
142 32
            ]
143 32
        ], $options);
144
145 32
        $options['location'] = $location;
146
147
        // If a version was set then add it to the headers.
148 32
        if (!empty($options['version'])) {
149 31
            $this->ewsHeaders['version'] = new SoapHeader(
150 31
                'http://schemas.microsoft.com/exchange/services/2006/types',
151 31
                'RequestServerVersion Version="'.$options['version'].'"'
152 31
            );
153 31
        }
154
155
        // If impersonation was set then add it to the headers.
156 32
        if (!empty($options['impersonation'])) {
157 1
            $impersonation = $options['impersonation'];
158 1
            if (is_string($impersonation)) {
159 1
                $impersonation = ExchangeImpersonation::fromEmailAddress($options['impersonation']);
160 1
            }
161
162 1
            $this->ewsHeaders['impersonation'] = new SoapHeader(
163 1
                'http://schemas.microsoft.com/exchange/services/2006/types',
164 1
                'ExchangeImpersonation',
165 1
                $impersonation->toXmlObject()
166 1
            );
167 1
        }
168
169 32
        if (!empty($options['timezone'])) {
170
            $this->ewsHeaders['timezone'] = new SoapHeader(
171
                'http://schemas.microsoft.com/exchange/services/2006/types',
172
                'TimeZoneContext',
173
                array(
174
                    'TimeZoneDefinition' => array(
175
                        'Id' => $options['timezone']
176
                    )
177
                )
178
            );
179
        }
180
181 32
        $this->httpPlayback = HttpPlayback::getInstance($options['httpPlayback']);
182
183 32
        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...
184 32
    }
185
186
    /**
187
     * Performs a SOAP request
188
     *
189
     * @link http://php.net/manual/en/function.soap-soapclient-dorequest.php
190
     *
191
     * @param string $request the xml soap request
192
     * @param string $location the url to request
193
     * @param string $action the soap action.
194
     * @param integer $version the soap version
195
     * @param integer $one_way
196
     * @return string the xml soap response.
197
     */
198 24
    public function __doRequest($request, $location, $action, $version, $one_way = 0)
199
    {
200
        $postOptions = array(
201 24
            'body' => $request,
202
            'headers' => array(
203 24
                'Connection' => 'Keep-Alive',
204 24
                'User-Agent' => 'PHP-SOAP-CURL',
205 24
                'Content-Type' => 'text/xml; charset=utf-8',
206
                'SOAPAction' => $action
207 24
            ),
208 24
            'verify' => $this->validate,
209
            'http_errors' => false
210 24
        );
211
212 24
        $postOptions = array_replace_recursive($postOptions, $this->auth);
213
214 24
        $client = $this->httpPlayback->getHttpClient();
215 24
        $response = $client->post($location, $postOptions);
216
217 24
        $this->__last_request_headers = $postOptions['headers'];
218 24
        $this->_responseCode = $response->getStatusCode();
219
220 24
        return $response->getBody()->__toString();
221
    }
222
223
    /**
224
     * Returns last SOAP request headers
225
     *
226
     * @link http://php.net/manual/en/function.soap-soapclient-getlastrequestheaders.php
227
     *
228
     * @return string the last soap request headers
229
     */
230
    public function __getLastRequestHeaders()
231
    {
232
        return implode('n', $this->__last_request_headers)."\n";
233
    }
234
235
    /**
236
     * Set validation certificate
237
     *
238
     * @param bool $validate
239
     * @return $this
240
     */
241 1
    public function validateCertificate($validate = true)
242
    {
243 1
        $this->validate = $validate;
244
245 1
        return $this;
246
    }
247
248
    /**
249
     * Returns the response code from the last request
250
     *
251
     * @return integer
252
     */
253 24
    public function getResponseCode()
254
    {
255 24
        return $this->_responseCode;
256
    }
257
}
258