├── .gitignore ├── .scrutinizer.yml ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── composer.json └── src └── SoapClient.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore files created by composer. 2 | composer.lock 3 | vendor/ 4 | .idea 5 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | checks: 2 | php: 3 | code_rating: true 4 | duplication: true 5 | tools: 6 | php_code_sniffer: 7 | config: 8 | standard: "PSR2" 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | 5 | ## 1.0.0 - 2017-10-03 6 | 7 | First stable release. 8 | 9 | ### Changed 10 | - Fixed broken URL in README. 11 | 12 | ## 1.0.0-beta.1 - 2016-12-30 13 | 14 | ### Added 15 | - Composer support. 16 | - Expect header to avoid potential hangs. 17 | - Method to retrieve the status code of the last request. 18 | - Last request/response method support. 19 | - Option to strip bad characters from the request. 20 | 21 | ### Changed 22 | - Moved out of the php-ews library into it's own project. 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009-2016 James I. Armes http://jamesarmes.com/php-ews/ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHP NTLM 2 | =================================== 3 | 4 | The PHP NTLM library (php-ntlm) is intended to provide various methods to aid in 5 | communicating with Microsoft services that utilize NTLM authentication from 6 | within PHP. 7 | 8 | [![Scrutinizer](https://img.shields.io/scrutinizer/g/jamesiarmes/php-ntlm.svg?style=flat-square)](https://scrutinizer-ci.com/g/jamesiarmes/php-ntlm) 9 | [![Total Downloads](https://img.shields.io/packagist/dt/jamesiarmes/php-ntlm.svg?style=flat-square)](https://packagist.org/packages/jamesiarmes/php-ntlm) 10 | 11 | Dependencies 12 | ------------ 13 | 14 | * Composer 15 | * PHP 8.1 16 | * cURL with NTLM support (7.23.0+ recommended) 17 | 18 | Installation 19 | ------------ 20 | 21 | The preferred installation method is via Composer, which will automatically 22 | handle autoloading of classes. 23 | 24 | ```json 25 | { 26 | "require": { 27 | "jamesiarmes/php-ntlm": "~1.0" 28 | } 29 | } 30 | ``` 31 | 32 | ## Usage 33 | 34 | ### SoapClient 35 | The `\jamesiarmes\PhpNtlm\SoapClient` class extends PHP's built in `SoapClient` 36 | class and can be used in the same manner with a few minor changes. 37 | 38 | 1. The constructor accepts a required 'user' and 'password' index in the 39 | `$options` array. 40 | 2. The constructor accepts an optional 'curlopts' index in the `$options` array 41 | that can be used to set or override the default curl options. 42 | 43 | Basic example: 44 | 45 | ```php 46 | $client = new SoapClient( 47 | $wsdl, 48 | array('user' => 'username', 'password' => '12345') 49 | ); 50 | ``` 51 | 52 | Example that skips SSL certificate validation: 53 | 54 | ```php 55 | $client = new SoapClient( 56 | $wsdl, 57 | array( 58 | 'user' => 'username', 59 | 'password' => '12345', 60 | 'curlopts' => array(CURLOPT_SSL_VERIFYPEER => false), 61 | ) 62 | ); 63 | ``` 64 | 65 | #### Available options 66 | The basic options available on the constructor can be found at 67 | http://php.net/manual/en/soapclient.soapclient.php. The trace option is not 68 | necessary, as the last request and response methods will always be available. In 69 | addition to these options, the following additional options are available: 70 | 71 | - user (string, required): The user to authenticate with. 72 | - password (string, required): The password to use when authenticating the user. 73 | - curlopts (array): Array of options to set on the curl handler when making the 74 | request. This can be used to override any cURL options with the exception of the 75 | following: CURLOPT_HEADER, CURLOPT_POST, CURLOPT_POSTFIELDS. 76 | - strip_bad_chars (boolean, default: true): Whether or not to strip invalid 77 | characters from the XML response. This can lead to content being returned 78 | differently than it actually is on the host service, but can also prevent the 79 | "looks like we got no XML document" SoapFault when the response includes invalid 80 | characters. 81 | - warn_on_bad_chars (boolean, default: false): Trigger a warning if bad 82 | characters are stripped. This has no affect unless strip_bad_chars is true. 83 | 84 | ## Projects that use php-ntlm 85 | The following is a list of known projects that use this library. If you would 86 | like to add your project to the list, please open a pull request to update this 87 | document. 88 | 89 | - [php-ews](https://github.com/jamesiarmes/php-ews) 90 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jamesiarmes/php-ntlm", 3 | "description": "Library for communicating with Microsoft services using NTLM authentication.", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "James Armes", 8 | "email": "jamesiarmes@gmail.com" 9 | } 10 | ], 11 | "prefer-stable": true, 12 | "require": { 13 | "php": ">=8.1", 14 | "ext-soap": "*", 15 | "ext-curl": "*" 16 | }, 17 | "config": { 18 | "bin-dir": "bin" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "jamesiarmes\\PhpNtlm\\": "src/" 23 | } 24 | }, 25 | "extra": { 26 | "branch-alias": { 27 | "dev-master": "1.0.x-dev" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/SoapClient.php: -------------------------------------------------------------------------------- 1 | null, 63 | 'password' => null, 64 | 'curlopts' => array(), 65 | 'strip_bad_chars' => true, 66 | 'warn_on_bad_chars' => false, 67 | ); 68 | $this->options = $options; 69 | 70 | // Verify that a user name and password were entered. 71 | if (empty($options['user']) || empty($options['password'])) { 72 | throw new \BadMethodCallException( 73 | 'A username and password is required.' 74 | ); 75 | } 76 | 77 | parent::__construct($wsdl, $options); 78 | } 79 | 80 | /** 81 | * {@inheritdoc} 82 | */ 83 | public function __doRequest(string $request, string $location, string $action, int $version, bool $oneWay = false): ?string 84 | { 85 | $headers = $this->buildHeaders($action); 86 | $this->__last_request = $request; 87 | $this->__last_request_headers = $headers; 88 | 89 | // Only reinitialize curl handle if the location is different. 90 | if (!$this->ch 91 | || curl_getinfo($this->ch, CURLINFO_EFFECTIVE_URL) != $location) { 92 | $this->ch = curl_init($location); 93 | } 94 | 95 | curl_setopt_array($this->ch, $this->curlOptions($action, $request)); 96 | $response = curl_exec($this->ch); 97 | 98 | // TODO: Add some real error handling. 99 | // If the response if false than there was an error and we should throw 100 | // an exception. 101 | if ($response === false) { 102 | $this->__last_response = $this->__last_response_headers = false; 103 | throw new \RuntimeException( 104 | 'Curl error: ' . curl_error($this->ch), 105 | curl_errno($this->ch) 106 | ); 107 | } 108 | 109 | $this->parseResponse($response); 110 | $this->cleanResponse(); 111 | 112 | return $this->__last_response; 113 | } 114 | 115 | /** 116 | * {@inheritdoc} 117 | */ 118 | public function __getLastRequestHeaders(): ?string 119 | { 120 | return implode("\n", $this->__last_request_headers) . "\n"; 121 | } 122 | 123 | /** 124 | * Returns the response code from the last request 125 | * 126 | * @throws \BadMethodCallException 127 | * If no cURL resource has been initialized. 128 | */ 129 | public function getResponseCode(): int 130 | { 131 | if (empty($this->ch)) { 132 | throw new \BadMethodCallException('No cURL resource has been ' 133 | . 'initialized. This is probably because no request has not ' 134 | . 'been made.'); 135 | } 136 | 137 | return curl_getinfo($this->ch, CURLINFO_HTTP_CODE); 138 | } 139 | 140 | /** 141 | * Builds the headers for the request. 142 | */ 143 | protected function buildHeaders(string $action): array 144 | { 145 | return array( 146 | 'Method: POST', 147 | 'Connection: Keep-Alive', 148 | 'User-Agent: PHP-SOAP-CURL', 149 | 'Content-Type: text/xml; charset=utf-8', 150 | "SOAPAction: \"$action\"", 151 | 'Expect: 100-continue', 152 | ); 153 | } 154 | 155 | /** 156 | * Cleans the response body by stripping bad characters if instructed to. 157 | */ 158 | protected function cleanResponse() 159 | { 160 | // If the option to strip bad characters is not set, then we shouldn't 161 | // do anything here. 162 | if (!$this->options['strip_bad_chars']) { 163 | return; 164 | } 165 | 166 | // Strip invalid characters from the XML response body. 167 | $count = 0; 168 | $this->__last_response = preg_replace( 169 | '/(?!�?(9|A|D))(&#x[0-1]?[0-9A-F];)/', 170 | ' ', 171 | $this->__last_response, 172 | -1, 173 | $count 174 | ); 175 | 176 | // If the option to warn on bad characters is set, and some characters 177 | // were stripped, then trigger a warning. 178 | if ($this->options['warn_on_bad_chars'] && $count > 0) { 179 | trigger_error( 180 | 'Invalid characters were stripped from the XML SOAP response.', 181 | E_USER_WARNING 182 | ); 183 | } 184 | } 185 | 186 | /** 187 | * Builds an array of curl options for the request 188 | */ 189 | protected function curlOptions(string $action, string $request): array 190 | { 191 | $options = $this->options['curlopts'] + array( 192 | CURLOPT_SSL_VERIFYPEER => true, 193 | CURLOPT_RETURNTRANSFER => true, 194 | CURLOPT_HTTPHEADER => $this->buildHeaders($action), 195 | CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, 196 | CURLOPT_HTTPAUTH => CURLAUTH_BASIC | CURLAUTH_NTLM, 197 | CURLOPT_USERPWD => $this->options['user'] . ':' 198 | . $this->options['password'], 199 | ); 200 | 201 | // We shouldn't allow these options to be overridden. 202 | $options[CURLOPT_HEADER] = true; 203 | $options[CURLOPT_POST] = true; 204 | $options[CURLOPT_POSTFIELDS] = $request; 205 | 206 | return $options; 207 | } 208 | 209 | /** 210 | * Pareses the response from a successful request. 211 | * 212 | * @param string $response 213 | * The response from the cURL request, including headers and body. 214 | */ 215 | public function parseResponse(string $response) 216 | { 217 | // Parse the response and set the last response and headers. 218 | $info = curl_getinfo($this->ch); 219 | $this->__last_response_headers = substr( 220 | $response, 221 | 0, 222 | $info['header_size'] 223 | ); 224 | $this->__last_response = substr($response, $info['header_size']); 225 | } 226 | } 227 | --------------------------------------------------------------------------------