Completed
Push — master ( 4c2102...7746c2 )
by Gareth
03:07
created

NTLMSoapClient::__call()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5

Importance

Changes 5
Bugs 1 Features 1
Metric Value
c 5
b 1
f 1
dl 0
loc 21
ccs 9
cts 9
cp 1
rs 8.7624
cc 5
eloc 11
nc 4
nop 2
crap 5
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
    /**
88
     * @TODO: Make this smarter. It should know and search what headers to remove on what actions
89 18
     *
90
     * @param string $name
91
     * @param string $args
92
     * @return mixed
93
     */
94
    public function __call($name, $args)
95
    {
96
        //If the request passed in is our custom type, let's use the toXmlObject function, since that's what it's built
97
        //for
98
        if (isset($args[0]) && $args[0] instanceof Type) {
99 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...
100
        }
101 27
102
        $this->__default_headers = array (
103 27
            $this->ewsHeaders['version'],
104
            $this->ewsHeaders['impersonation']
105
        );
106 27
107 27
        if ($name != "DeleteItem" && $name != "SyncFolderItems") {
108
            $this->__default_headers[] = $this->ewsHeaders['timezone'];
109 27
        }
110
111
        $response = parent::__call($name, $args);
112 27
        $this->__default_headers = [];
113 26
        return $response;
114 26
    }
115 26
116 26
    /**
117 26
     * @param mixed $location
118
     * @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...
119
     * @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...
120 27
     * @param $wsdl
121 2
     * @param array $options
122 2
     */
123 2
    public function __construct($location, $auth, $wsdl, $options = array())
124 2
    {
125
        $this->auth = $auth;
126 2
127 2
        $options = array_replace_recursive([
128 2
            'httpPlayback' => [
129 2
                'mode' => null
130 2
            ]
131 2
        ], $options);
132
133 27
        $options['location'] = $location;
134
135
        // If a version was set then add it to the headers.
136
        if (!empty($options['version'])) {
137
            $this->ewsHeaders['version'] = new SoapHeader(
138
                'http://schemas.microsoft.com/exchange/services/2006/types',
139
                'RequestServerVersion Version="' . $options['version'] . '"'
140
            );
141
        }
142
143
        // If impersonation was set then add it to the headers.
144
        if (!empty($options['impersonation'])) {
145 27
            $impersonation = $options['impersonation'];
146
            if (is_string($impersonation)) {
147 27
                $impersonation = ExchangeImpersonation::fromEmailAddress($options['impersonation']);
148 27
            }
149
150
            $this->ewsHeaders['impersonation'] = new SoapHeader(
151
                'http://schemas.microsoft.com/exchange/services/2006/types',
152
                'ExchangeImpersonation',
153
                $impersonation->toXmlObject()
154
            );
155
        }
156
157
        if (!empty($options['timezone'])) {
158
            $this->ewsHeaders['timezone'] = new SoapHeader(
159
                'http://schemas.microsoft.com/exchange/services/2006/types',
160
                'TimeZoneContext',
161
                array(
162 18
                    'TimeZoneDefinition' => array(
163
                        'Id' => $options['timezone']
164
                    )
165 18
                )
166
            );
167 18
        }
168 18
169 18
        $this->httpPlayback = HttpPlayback::getInstance($options['httpPlayback']);
170
171 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...
172 18
    }
173
174 18
    /**
175
     * Performs a SOAP request
176 18
     *
177
     * @link http://php.net/manual/en/function.soap-soapclient-dorequest.php
178 18
     *
179 18
     * @param string $request the xml soap request
180
     * @param string $location the url to request
181 18
     * @param string $action the soap action.
182 18
     * @param integer $version the soap version
183
     * @param integer $one_way
184 18
     * @return string the xml soap response.
185
     */
186
    public function __doRequest($request, $location, $action, $version, $one_way = 0)
187
    {
188
        $postOptions = array(
189
            'body' => $request,
190
            'headers' => array(
191
                'Connection' => 'Keep-Alive',
192
                'User-Agent' => 'PHP-SOAP-CURL',
193
                'Content-Type' => 'text/xml; charset=utf-8',
194
                'SOAPAction' => $action
195
            ),
196
            'verify' => $this->validate,
197
            'http_errors' => false
198
        );
199
200
        $postOptions = array_replace_recursive($postOptions, $this->auth);
201
202
        $client = $this->httpPlayback->getHttpClient();
203
        $response = $client->post($location, $postOptions);
204
205 1
        $this->__last_request_headers = $postOptions['headers'];
206
        $this->_responseCode = $response->getStatusCode();
207 1
208
        return $response->getBody()->__toString();
209 1
    }
210
211
    /**
212
     * Returns last SOAP request headers
213
     *
214
     * @link http://php.net/manual/en/function.soap-soapclient-getlastrequestheaders.php
215
     *
216
     * @return string the last soap request headers
217 18
     */
218
    public function __getLastRequestHeaders()
219 18
    {
220
        return implode('n', $this->__last_request_headers) . "\n";
221
    }
222
223
    /**
224
     * Set validation certificate
225
     *
226
     * @param bool $validate
227
     * @return $this
228
     */
229
    public function validateCertificate($validate = true)
230
    {
231
        $this->validate = $validate;
232
233
        return $this;
234
    }
235
236
    /**
237
     * Returns the response code from the last request
238
     *
239
     * @return integer
240
     */
241
    public function getResponseCode()
242
    {
243
        return $this->_responseCode;
244
    }
245
}
246