blob: 41334b6890d004b0730af93b6441117c3f6a15a1 [file] [log] [blame]
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001<?php
2
3namespace LdapRecord;
4
5use Closure;
6use ErrorException;
7use Exception;
8
9trait HandlesConnection
10{
11 /**
12 * The LDAP host that is currently connected.
13 *
14 * @var string|null
15 */
16 protected $host;
17
18 /**
19 * The LDAP connection resource.
20 *
21 * @var resource|null
22 */
23 protected $connection;
24
25 /**
26 * The bound status of the connection.
27 *
28 * @var bool
29 */
30 protected $bound = false;
31
32 /**
33 * Whether the connection must be bound over SSL.
34 *
35 * @var bool
36 */
37 protected $useSSL = false;
38
39 /**
40 * Whether the connection must be bound over TLS.
41 *
42 * @var bool
43 */
44 protected $useTLS = false;
45
46 /**
47 * @inheritdoc
48 */
49 public function isUsingSSL()
50 {
51 return $this->useSSL;
52 }
53
54 /**
55 * @inheritdoc
56 */
57 public function isUsingTLS()
58 {
59 return $this->useTLS;
60 }
61
62 /**
63 * @inheritdoc
64 */
65 public function isBound()
66 {
67 return $this->bound;
68 }
69
70 /**
71 * @inheritdoc
72 */
73 public function isConnected()
74 {
75 return ! is_null($this->connection);
76 }
77
78 /**
79 * @inheritdoc
80 */
81 public function canChangePasswords()
82 {
83 return $this->isUsingSSL() || $this->isUsingTLS();
84 }
85
86 /**
87 * @inheritdoc
88 */
89 public function ssl($enabled = true)
90 {
91 $this->useSSL = $enabled;
92
93 return $this;
94 }
95
96 /**
97 * @inheritdoc
98 */
99 public function tls($enabled = true)
100 {
101 $this->useTLS = $enabled;
102
103 return $this;
104 }
105
106 /**
107 * @inheritdoc
108 */
109 public function setOptions(array $options = [])
110 {
111 foreach ($options as $option => $value) {
112 $this->setOption($option, $value);
113 }
114 }
115
116 /**
117 * @inheritdoc
118 */
119 public function getHost()
120 {
121 return $this->host;
122 }
123
124 /**
125 * @inheritdoc
126 */
127 public function getConnection()
128 {
129 return $this->connection;
130 }
131
132 /**
133 * @inheritdoc
134 */
135 public function getProtocol()
136 {
137 return $this->isUsingSSL() ? LdapInterface::PROTOCOL_SSL : LdapInterface::PROTOCOL;
138 }
139
140 /**
141 * @inheritdoc
142 */
143 public function getExtendedError()
144 {
145 return $this->getDiagnosticMessage();
146 }
147
148 /**
149 * Convert warnings to exceptions for the given operation.
150 *
151 * @param Closure $operation
152 *
153 * @throws LdapRecordException
154 *
155 * @return mixed
156 */
157 protected function executeFailableOperation(Closure $operation)
158 {
159 // If some older versions of PHP, errors are reported instead of throwing
160 // exceptions, which could be a signifcant detriment to our application.
161 // Here, we will enforce these operations to throw exceptions instead.
162 set_error_handler(function ($severity, $message, $file, $line) {
163 if (! $this->shouldBypassError($message)) {
164 throw new ErrorException($message, $severity, $severity, $file, $line);
165 }
166 });
167
168 try {
169 if (($result = $operation()) !== false) {
170 return $result;
171 }
172
173 // If the failed query operation was a based on a query being executed
174 // -- such as a search, read, or listing, then we can safely return
175 // the failed response here and prevent throwning an exception.
176 if ($this->shouldBypassFailure($method = debug_backtrace()[1]['function'])) {
177 return $result;
178 }
179
180 throw new Exception("LDAP operation [$method] failed.");
181 } catch (ErrorException $e) {
182 throw LdapRecordException::withDetailedError($e, $this->getDetailedError());
183 } finally {
184 restore_error_handler();
185 }
186 }
187
188 /**
189 * Determine if the failed operation should be bypassed.
190 *
191 * @param string $method
192 *
193 * @return bool
194 */
195 protected function shouldBypassFailure($method)
196 {
197 return in_array($method, ['search', 'read', 'listing']);
198 }
199
200 /**
201 * Determine if the error should be bypassed.
202 *
203 * @param string $error
204 *
205 * @return bool
206 */
207 protected function shouldBypassError($error)
208 {
209 return $this->causedByPaginationSupport($error) || $this->causedBySizeLimit($error) || $this->causedByNoSuchObject($error);
210 }
211
212 /**
213 * Determine if the current PHP version supports server controls.
214 *
215 * @deprecated since v2.5.0
216 *
217 * @return bool
218 */
219 public function supportsServerControlsInMethods()
220 {
221 return version_compare(PHP_VERSION, '7.3.0') >= 0;
222 }
223
224 /**
225 * Generates an LDAP connection string for each host given.
226 *
227 * @param string|array $hosts
228 * @param string $port
229 *
230 * @return string
231 */
232 protected function makeConnectionUris($hosts, $port)
233 {
234 // If an attempt to connect via SSL protocol is being performed,
235 // and we are still using the default port, we will swap it
236 // for the default SSL port, for developer convenience.
237 if ($this->isUsingSSL() && $port == LdapInterface::PORT) {
238 $port = LdapInterface::PORT_SSL;
239 }
240
241 // The blank space here is intentional. PHP's LDAP extension
242 // requires additional hosts to be seperated by a blank
243 // space, so that it can parse each individually.
244 return implode(' ', $this->assembleHostUris($hosts, $port));
245 }
246
247 /**
248 * Assemble the host URI strings.
249 *
250 * @param array|string $hosts
251 * @param string $port
252 *
253 * @return array
254 */
255 protected function assembleHostUris($hosts, $port)
256 {
257 return array_map(function ($host) use ($port) {
258 return "{$this->getProtocol()}{$host}:{$port}";
259 }, (array) $hosts);
260 }
261}