Completed
Pull Request — master (#1854)
by
unknown
02:20
created

LdapContactsSuggestions::SetAllowedEmails()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISuggestions
4
{
5
	/**
6
	 * @var string
7
	 */
8
	private $sHostName = '127.0.0.1';
9
10
	/**
11
	 * @var int
12
	 */
13
	private $iHostPort = 389;
14
15
	/**
16
	 * @var string
17
	 */
18
	private $sAccessDn = null;
19
20
	/**
21
	 * @var string
22
	 */
23
	private $sAccessPassword = null;
24
25
	/**
26
	 * @var string
27
	 */
28
	private $sUsersDn = '';
29
30
	/**
31
	 * @var string
32
	 */
33
	private $sObjectClass = 'inetOrgPerson';
34
35
	/**
36
	 * @var string
37
	 */
38
	private $sUidField = 'uid';
39
40
	/**
41
	 * @var string
42
	 */
43
	private $sNameField = 'givenname';
44
45
	/**
46
	 * @var string
47
	 */
48
	private $sEmailField = 'mail';
49
50
	/**
51
	 * @var \MailSo\Log\Logger
52
	 */
53
	private $oLogger = null;
54
55
	/**
56
	 * @var string
57
	 */
58
	private $sAllowedEmails = '';
59
60
	/**
61
	 * @param string $sHostName
62
	 * @param int $iHostPort
63
	 * @param string $sAccessDn
64
	 * @param string $sAccessPassword
65
	 * @param string $sUsersDn
66
	 * @param string $sObjectClass
67
	 * @param string $sNameField
68
	 * @param string $sEmailField
69
	 *
70
	 * @return \LdapContactsSuggestions
71
	 */
72
	public function SetConfig($sHostName, $iHostPort, $sAccessDn, $sAccessPassword, $sUsersDn, $sObjectClass, $sUidField, $sNameField, $sEmailField)
73
	{
74
		$this->sHostName = $sHostName;
75
		$this->iHostPort = $iHostPort;
76
		if (0 < \strlen($sAccessDn))
77
		{
78
			$this->sAccessDn = $sAccessDn;
79
			$this->sAccessPassword = $sAccessPassword;
80
		}
81
		$this->sUsersDn = $sUsersDn;
82
		$this->sObjectClass = $sObjectClass;
83
		$this->sUidField = $sUidField;
84
		$this->sNameField = $sNameField;
85
		$this->sEmailField = $sEmailField;
86
87
		return $this;
88
	}
89
90
	/**
91
	 * @param string $sAllowedEmails
92
	 *
93
	 * @return \LdapContactsSuggestions
94
	 */
95
	public function SetAllowedEmails($sAllowedEmails)
96
	{
97
		$this->sAllowedEmails = $sAllowedEmails;
98
99
		return $this;
100
	}
101
102
	/**
103
	 * @param \RainLoop\Model\Account $oAccount
104
	 * @param string $sQuery
105
	 * @param int $iLimit = 20
106
	 *
107
	 * @return array
108
	 */
109
	public function Process($oAccount, $sQuery, $iLimit = 20)
110
	{
111
		$sQuery = \trim($sQuery);
112
113
		if (2 > \strlen($sQuery))
114
		{
115
			return array();
116
		}
117
		else if (!$oAccount || !\RainLoop\Plugins\Helper::ValidateWildcardValues($oAccount->Email(), $this->sAllowedEmails))
118
		{
119
			return array();
120
		}
121
122
		$aResult = $this->ldapSearch($oAccount, $sQuery);
123
124
		$aResult = \RainLoop\Utils::RemoveSuggestionDuplicates($aResult);
125
		if ($iLimit < \count($aResult))
126
		{
127
			$aResult = \array_slice($aResult, 0, $iLimit);
128
		}
129
130
		return $aResult;
131
	}
132
133
	/**
134
	 * @param array $aLdapItem
135
	 * @param array $aEmailFields
136
	 * @param array $aNameFields
137
	 *
138
	 * @return array
139
	 */
140
	private function findNameAndEmail($aLdapItem, $aEmailFields, $aNameFields, $aUidFields)
141
	{
142
		$sEmail = $sName = $sUid = '';
143
		if ($aLdapItem)
0 ignored issues
show
Bug Best Practice introduced by
The expression $aLdapItem of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
144
		{
145 View Code Duplication
			foreach ($aEmailFields as $sField)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
146
			{
147
				if (!empty($aLdapItem[$sField][0]))
148
				{
149
					$sEmail = \trim($aLdapItem[$sField][0]);
150
					if (!empty($sEmail))
151
					{
152
						break;
153
					}
154
				}
155
			}
156
157
			foreach ($aNameFields as $sField)
158
			{
159
				if (!empty($aLdapItem[$sField][0]))
160
				{
161
					# The attribute is multi-valued
162
					$value = $aLdapItem[$sField][0];
163
				} else if ( defined($aLdapItem[$sField]) && is_string($aLdapItem[$sField]) )
164
				{
165
					# The attribute is mono-valued
166
					$value = $aLdapItem[$sField];
167
				}
168
				if (!empty($value))
169
				{
170
					$sName = \trim($value);
171
					if (!empty($sName))
172
					{
173
						break;
174
					}
175
				}
176
			}
177
178 View Code Duplication
			foreach ($aUidFields as $sField)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
179
			{
180
				if (!empty($aLdapItem[$sField][0]))
181
				{
182
					$sUid = \trim($aLdapItem[$sField][0]);
183
					if (!empty($sUid))
184
					{
185
						break;
186
					}
187
				}
188
			}
189
		}
190
191
		return array($sEmail, $sName, $sUid);
192
	}
193
194
	/**
195
	 * @param \RainLoop\Model\Account $oAccount
196
	 * @param string $sQuery
197
	 *
198
	 * @return array
199
	 */
200
	private function ldapSearch($oAccount, $sQuery)
201
	{
202
		$sSearchEscaped = $this->escape($sQuery);
203
204
		$aResult = array();
205
		$oCon = @\ldap_connect($this->sHostName, $this->iHostPort);
206
		if ($oCon)
207
		{
208
			$this->oLogger->Write('ldap_connect: connected', \MailSo\Log\Enumerations\Type::INFO, 'LDAP');
209
210
			@\ldap_set_option($oCon, LDAP_OPT_PROTOCOL_VERSION, 3);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
211
212
			if (!@\ldap_bind($oCon, $this->sAccessDn, $this->sAccessPassword))
213
			{
214
				if (is_null($this->sAccessDn))
215
				{
216
					$this->logLdapError($oCon, 'ldap_bind (anonymous)');
217
				}
218
				else
219
				{
220
					$this->logLdapError($oCon, 'ldap_bind');
221
				}
222
223
				return $aResult;
224
			}
225
226
			$sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email());
227
			$sSearchDn = \strtr($this->sUsersDn, array(
228
				'{domain}' => $sDomain,
229
				'{domain:dc}' => 'dc='.\strtr($sDomain, array('.' => ',dc=')),
230
				'{email}' => $oAccount->Email(),
231
				'{email:user}' => \MailSo\Base\Utils::GetAccountNameFromEmail($oAccount->Email()),
232
				'{email:domain}' => $sDomain,
233
				'{login}' => $oAccount->Login(),
234
				'{imap:login}' => $oAccount->Login(),
235
				'{imap:host}' => $oAccount->DomainIncHost(),
236
				'{imap:port}' => $oAccount->DomainIncPort()
237
			));
238
239
			$aEmails = empty($this->sEmailField) ? array() : \explode(',', $this->sEmailField);
240
			$aNames = empty($this->sNameField) ? array() : \explode(',', $this->sNameField);
241
			$aUIDs = empty($this->sUidField) ? array() : \explode(',', $this->sUidField);
242
243
			$aEmails = \array_map('trim', $aEmails);
244
			$aNames = \array_map('trim', $aNames);
245
			$aUIDs = \array_map('trim', $aUIDs);
246
			$aEmails = \array_map('strtolower', $aEmails);
247
			$aNames = \array_map('strtolower', $aNames);
248
			$aUIDs = \array_map('strtolower', $aUIDs);
249
250
			$aFields = \array_merge($aEmails, $aNames, $aUIDs);
251
252
			$aItems = array();
253
			$sSubFilter = '';
254
			foreach ($aFields as $sItem)
255
			{
256
				if (!empty($sItem))
257
				{
258
					$aItems[] = $sItem;
259
					$sSubFilter .= '('.$sItem.'=*'.$sSearchEscaped.'*)';
260
				}
261
			}
262
263
			$sFilter = '(&(objectclass='.$this->sObjectClass.')';
264
			$sFilter .= (1 < count($aItems) ? '(|' : '').$sSubFilter.(1 < count($aItems) ? ')' : '');
265
			$sFilter .= ')';
266
267
			$this->oLogger->Write('ldap_search: start: '.$sSearchDn.' / '.$sFilter, \MailSo\Log\Enumerations\Type::INFO, 'LDAP');
268
			$oS = @\ldap_search($oCon, $sSearchDn, $sFilter, $aItems, 0, 30, 30);
269
			if ($oS)
270
			{
271
				$aEntries = @\ldap_get_entries($oCon, $oS);
272
				if (is_array($aEntries))
273
				{
274
					if (isset($aEntries['count']))
275
					{
276
						unset($aEntries['count']);
277
					}
278
279
					foreach ($aEntries as $aItem)
280
					{
281
						if ($aItem)
282
						{
283
							$sName = $sEmail = '';
284
							list ($sEmail, $sName) = $this->findNameAndEmail($aItem, $aEmails, $aNames, $aUIDs);
285
							if (!empty($sEmail))
286
							{
287
								$aResult[] = array($sEmail, $sName);
288
							}
289
						}
290
					}
291
				}
292
				else
293
				{
294
					$this->logLdapError($oCon, 'ldap_get_entries');
295
				}
296
			}
297
			else
298
			{
299
				$this->logLdapError($oCon, 'ldap_search');
300
			}
301
		}
302
		else
303
		{
304
			return $aResult;
305
		}
306
307
		return $aResult;
308
	}
309
310
	/**
311
	 * @param string $sStr
312
	 *
313
	 * @return string
314
	 */
315
	public function escape($sStr)
316
	{
317
		$aNewChars = array();
318
		$aChars = array('\\', '*', '(', ')', \chr(0));
319
320
		foreach ($aChars as $iIndex => $sValue)
321
		{
322
			$aNewChars[$iIndex] = '\\'.\str_pad(\dechex(\ord($sValue)), 2, '0');
323
		}
324
325
		return \str_replace($aChars, $aNewChars, $sStr);
326
	}
327
328
	/**
329
	 * @param mixed $oCon
330
	 * @param string $sCmd
331
	 *
332
	 * @return string
333
	 */
334
	public function logLdapError($oCon, $sCmd)
335
	{
336
		if ($this->oLogger)
337
		{
338
			$sError = $oCon ? @\ldap_error($oCon) : '';
339
			$iErrno = $oCon ? @\ldap_errno($oCon) : 0;
340
341
			$this->oLogger->Write($sCmd.' error: '.$sError.' ('.$iErrno.')',
342
				\MailSo\Log\Enumerations\Type::WARNING, 'LDAP');
343
		}
344
	}
345
346
	/**
347
	 * @param \MailSo\Log\Logger $oLogger
348
	 *
349
	 * @return \LdapContactsSuggestions
350
	 */
351
	public function SetLogger($oLogger)
352
	{
353
		if ($oLogger instanceof \MailSo\Log\Logger)
0 ignored issues
show
Bug introduced by
The class MailSo\Log\Logger does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
354
		{
355
			$this->oLogger = $oLogger;
356
		}
357
358
		return $this;
359
	}
360
}
361