access.php 48.4 KB
Newer Older
1
2
3
<?php

/**
4
 * ownCloud – LDAP Access
5
6
 *
 * @author Arthur Schiwon
Arthur Schiwon's avatar
Arthur Schiwon committed
7
 * @copyright 2012, 2013 Arthur Schiwon blizzz@owncloud.com
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

24
namespace OCA\user_ldap\lib;
25

26
27
28
29
/**
 * Class Access
 * @package OCA\user_ldap\lib
 */
30
class Access extends LDAPUtility implements user\IUserTools {
Arthur Schiwon's avatar
Arthur Schiwon committed
31
32
33
	/**
	 * @var \OCA\user_ldap\lib\Connection
	 */
34
	public $connection;
35
	public $userManager;
36
37
38
	//never ever check this var directly, always use getPagedSearchResultState
	protected $pagedSearchedSuccessful;

39
40
41
	/**
	 * @var string[] $cookies an array of returned Paged Result cookies
	 */
Arthur Schiwon's avatar
Arthur Schiwon committed
42
43
	protected $cookies = array();

44
45
46
47
48
	/**
	 * @var string $lastCookie the last cookie returned from a Paged Results
	 * operation, defaults to an empty string
	 */
	protected $lastCookie = '';
49

50
51
	public function __construct(Connection $connection, ILDAPWrapper $ldap,
		user\Manager $userManager) {
52
		parent::__construct($ldap);
53
		$this->connection = $connection;
54
55
		$this->userManager = $userManager;
		$this->userManager->setLdapAccess($this);
56
57
	}

58
59
60
	/**
	 * @return bool
	 */
61
62
	private function checkConnection() {
		return ($this->connection instanceof Connection);
Arthur Schiwon's avatar
Arthur Schiwon committed
63
	}
Arthur Schiwon's avatar
Arthur Schiwon committed
64

65
	/**
66
	 * returns the Connection instance
67
68
69
70
71
72
73
	 * @return \OCA\user_ldap\lib\Connection
	 */
	public function getConnection() {
		return $this->connection;
	}

	/**
74
	 * reads a given attribute for an LDAP record identified by a DN
Arthur Schiwon's avatar
Arthur Schiwon committed
75
76
	 * @param string $dn the record in question
	 * @param string $attr the attribute that shall be retrieved
77
	 *        if empty, just check the record's existence
78
79
	 * @param string $filter
	 * @return array|false an array of values on success or an empty
80
	 *          array if $attr is empty, false otherwise
81
	 */
82
	public function readAttribute($dn, $attr, $filter = 'objectClass=*') {
83
		if(!$this->checkConnection()) {
Bart Visscher's avatar
Bart Visscher committed
84
85
86
			\OCP\Util::writeLog('user_ldap',
				'No LDAP Connector assigned, access impossible for readAttribute.',
				\OCP\Util::WARN);
87
			return false;
Arthur Schiwon's avatar
Arthur Schiwon committed
88
		}
89
		$cr = $this->connection->getConnectionResource();
90
		if(!$this->ldap->isResource($cr)) {
91
			//LDAP not available
92
			\OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', \OCP\Util::DEBUG);
93
94
			return false;
		}
95
96
97
		//Cancel possibly running Paged Results operation, otherwise we run in
		//LDAP protocol errors
		$this->abandonPagedSearch();
98
99
		// openLDAP requires that we init a new Paged Search. Not needed by AD,
		// but does not hurt either.
100
		$this->initPagedSearch($filter, array($dn), array($attr), 1, 0);
Arthur Schiwon's avatar
Arthur Schiwon committed
101
		$dn = $this->DNasBaseParameter($dn);
102
		$rr = @$this->ldap->read($cr, $dn, $filter, array($attr));
103
		if(!$this->ldap->isResource($rr)) {
104
105
106
107
			if(!empty($attr)) {
				//do not throw this message on userExists check, irritates
				\OCP\Util::writeLog('user_ldap', 'readAttribute failed for DN '.$dn, \OCP\Util::DEBUG);
			}
108
109
110
			//in case an error occurs , e.g. object does not exist
			return false;
		}
111
112
		if (empty($attr)) {
			\OCP\Util::writeLog('user_ldap', 'readAttribute: '.$dn.' found', \OCP\Util::DEBUG);
113
			return array();
114
		}
115
		$er = $this->ldap->firstEntry($cr, $rr);
116
		if(!$this->ldap->isResource($er)) {
117
118
119
			//did not match the filter, return false
			return false;
		}
120
		//LDAP attributes are not case sensitive
Arthur Schiwon's avatar
Arthur Schiwon committed
121
122
		$result = \OCP\Util::mb_array_change_key_case(
				$this->ldap->getAttributes($cr, $er), MB_CASE_LOWER, 'UTF-8');
123
		$attr = mb_strtolower($attr, 'UTF-8');
124

Arthur Schiwon's avatar
Arthur Schiwon committed
125
		if(isset($result[$attr]) && $result[$attr]['count'] > 0) {
126
127
			$values = array();
			for($i=0;$i<$result[$attr]['count'];$i++) {
128
129
				if($this->resemblesDN($attr)) {
					$values[] = $this->sanitizeDN($result[$attr][$i]);
130
				} elseif(strtolower($attr) === 'objectguid' || strtolower($attr) === 'guid') {
131
132
133
134
					$values[] = $this->convertObjectGUID2Str($result[$attr][$i]);
				} else {
					$values[] = $result[$attr][$i];
				}
135
136
137
			}
			return $values;
		}
138
		\OCP\Util::writeLog('user_ldap', 'Requested attribute '.$attr.' not found for '.$dn, \OCP\Util::DEBUG);
139
		return false;
Arthur Schiwon's avatar
Arthur Schiwon committed
140
141
	}

142
	/**
143
	 * checks whether the given attributes value is probably a DN
144
145
	 * @param string $attr the attribute in question
	 * @return boolean if so true, otherwise false
146
	 */
147
148
149
150
151
	private function resemblesDN($attr) {
		$resemblingAttributes = array(
			'dn',
			'uniquemember',
			'member'
Arthur Schiwon's avatar
Arthur Schiwon committed
152
		);
153
154
		return in_array($attr, $resemblingAttributes);
	}
155

156
157
158
159
160
161
162
163
164
165
166
167
	/**
	 * checks whether the given string is probably a DN
	 * @param string $string
	 * @return boolean
	 */
	public function stringResemblesDN($string) {
		$r = $this->ldap->explodeDN($string, 0);
		// if exploding a DN succeeds and does not end up in
		// an empty array except for $r[count] being 0.
		return (is_array($r) && count($r) > 1);
	}

168
	/**
169
	 * sanitizes a DN received from the LDAP server
170
171
	 * @param array $dn the DN in question
	 * @return array the sanitized DN
172
173
	 */
	private function sanitizeDN($dn) {
Arthur Schiwon's avatar
Arthur Schiwon committed
174
175
176
177
		//treating multiple base DNs
		if(is_array($dn)) {
			$result = array();
			foreach($dn as $singleDN) {
Robin McCorkell's avatar
Robin McCorkell committed
178
				$result[] = $this->sanitizeDN($singleDN);
Arthur Schiwon's avatar
Arthur Schiwon committed
179
180
181
182
			}
			return $result;
		}

Bart Visscher's avatar
Bart Visscher committed
183
184
		//OID sometimes gives back DNs with whitespace after the comma
		// a la "uid=foo, cn=bar, dn=..." We need to tackle this!
Arthur Schiwon's avatar
Arthur Schiwon committed
185
		$dn = preg_replace('/([^\\\]),(\s+)/u', '\1,', $dn);
186

187
188
189
		//make comparisons and everything work
		$dn = mb_strtolower($dn, 'UTF-8');

Arthur Schiwon's avatar
Arthur Schiwon committed
190
191
192
		//escape DN values according to RFC 2253 – this is already done by ldap_explode_dn
		//to use the DN in search filters, \ needs to be escaped to \5c additionally
		//to use them in bases, we convert them back to simple backslashes in readAttribute()
193
194
195
196
197
198
199
200
201
		$replacements = array(
			'\,' => '\5c2C',
			'\=' => '\5c3D',
			'\+' => '\5c2B',
			'\<' => '\5c3C',
			'\>' => '\5c3E',
			'\;' => '\5c3B',
			'\"' => '\5c22',
			'\#' => '\5c23',
202
203
204
			'('  => '\28',
			')'  => '\29',
			'*'  => '\2A',
205
		);
206
		$dn = str_replace(array_keys($replacements), array_values($replacements), $dn);
207

208
		return $dn;
209
210
	}

Arthur Schiwon's avatar
Arthur Schiwon committed
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
	/**
	 * returns a DN-string that is cleaned from not domain parts, e.g.
	 * cn=foo,cn=bar,dc=foobar,dc=server,dc=org
	 * becomes dc=foobar,dc=server,dc=org
	 * @param string $dn
	 * @return string
	 */
	public function getDomainDNFromDN($dn) {
		$allParts = $this->ldap->explodeDN($dn, 0);
		if($allParts === false) {
			//not a valid DN
			return '';
		}
		$domainParts = array();
		$dcFound = false;
		foreach($allParts as $part) {
			if(!$dcFound && strpos($part, 'dc=') === 0) {
				$dcFound = true;
			}
			if($dcFound) {
				$domainParts[] = $part;
			}
		}
		$domainDN = implode(',', $domainParts);
		return $domainDN;
	}

238
239
	/**
	 * gives back the database table for the query
240
241
	 * @param bool $isUser
	 * @return string
242
	 */
243
	private function getMapTable($isUser) {
244
245
246
247
248
249
250
251
		if($isUser) {
			return '*PREFIX*ldap_user_mapping';
		} else {
			return '*PREFIX*ldap_group_mapping';
		}
	}

	/**
252
	 * returns the LDAP DN for the given internal ownCloud name of the group
253
	 * @param string $name the ownCloud name in question
254
	 * @return string with the LDAP DN on success, otherwise false
255
	 */
256
	public function groupname2dn($name) {
257
258
259
260
261
262
263
		$dn = $this->ocname2dn($name, false);

		if($dn) {
			return $dn;
		}

		return false;
264
265
266
	}

	/**
267
	 * returns the LDAP DN for the given internal ownCloud name of the user
268
269
	 * @param string $name the ownCloud name in question
	 * @return string with the LDAP DN on success, otherwise false
270
	 */
271
272
	public function username2dn($name) {
		$dn = $this->ocname2dn($name, true);
273
274
275
		//Check whether the DN belongs to the Base, to avoid issues on multi-
		//server setups
		if($dn && $this->isDNPartOfBase($dn, $this->connection->ldapBaseUsers)) {
276
277
278
279
280
281
			return $dn;
		}

		return false;
	}

282
	/**
283
	 * returns the LDAP DN for the given internal ownCloud name
284
	 * @param string $name the ownCloud name in question
285
	 * @param boolean $isUser is it a user? otherwise group
286
	 * @return string with the LDAP DN on success, otherwise false
287
288
289
	 */
	private function ocname2dn($name, $isUser) {
		$table = $this->getMapTable($isUser);
290

291
		$query = \OCP\DB::prepare('
292
293
294
			SELECT `ldap_dn`
			FROM `'.$table.'`
			WHERE `owncloud_name` = ?
295
296
297
298
299
300
301
		');

		$record = $query->execute(array($name))->fetchOne();
		return $record;
	}

	/**
302
	 * returns the internal ownCloud name for the given LDAP DN of the group, false on DN outside of search DN or failure
303
304
305
	 * @param string $dn the dn of the group object
	 * @param string $ldapName optional, the display name of the object
	 * @return string with the name to use in ownCloud, false on DN outside of search DN
306
	 */
307
	public function dn2groupname($dn, $ldapName = null) {
Arthur Schiwon's avatar
Arthur Schiwon committed
308
309
310
		//To avoid bypassing the base DN settings under certain circumstances
		//with the group support, check whether the provided DN matches one of
		//the given Bases
Arthur Schiwon's avatar
Arthur Schiwon committed
311
		if(!$this->isDNPartOfBase($dn, $this->connection->ldapBaseGroups)) {
312
313
			return false;
		}
Arthur Schiwon's avatar
Arthur Schiwon committed
314

315
		return $this->dn2ocname($dn, $ldapName, false);
316
317
318
	}

	/**
319
	 * returns the internal ownCloud name for the given LDAP DN of the user, false on DN outside of search DN or failure
320
321
322
	 * @param string $dn the dn of the user object
	 * @param string $ldapName optional, the display name of the object
	 * @return string with with the name to use in ownCloud
323
	 */
324
	public function dn2username($dn, $ldapName = null) {
Arthur Schiwon's avatar
Arthur Schiwon committed
325
326
327
		//To avoid bypassing the base DN settings under certain circumstances
		//with the group support, check whether the provided DN matches one of
		//the given Bases
Arthur Schiwon's avatar
Arthur Schiwon committed
328
		if(!$this->isDNPartOfBase($dn, $this->connection->ldapBaseUsers)) {
329
330
			return false;
		}
Arthur Schiwon's avatar
Arthur Schiwon committed
331

332
		return $this->dn2ocname($dn, $ldapName, true);
333
334
	}

335
	/**
336
	 * returns an internal ownCloud name for the given LDAP DN, false on DN outside of search DN
337
338
339
340
	 * @param string $dn the dn of the user object
	 * @param string $ldapName optional, the display name of the object
	 * @param bool $isUser optional, whether it is a user object (otherwise group assumed)
	 * @return string with with the name to use in ownCloud
341
	 */
342
	public function dn2ocname($dn, $ldapName = null, $isUser = true) {
343
		$table = $this->getMapTable($isUser);
344
		if($isUser) {
345
			$fncFindMappedName = 'findMappedUser';
346
			$nameAttribute = $this->connection->ldapUserDisplayName;
347
		} else {
348
			$fncFindMappedName = 'findMappedGroup';
349
			$nameAttribute = $this->connection->ldapGroupDisplayName;
350
351
		}

352
		//let's try to retrieve the ownCloud name from the mappings table
353
354
355
		$ocName = $this->$fncFindMappedName($dn);
		if($ocName) {
			return $ocName;
356
357
		}

358
		//second try: get the UUID and check if it is known. Then, update the DN and return the name.
359
		$uuid = $this->getUUID($dn, $isUser);
360
361
		if($uuid) {
			$query = \OCP\DB::prepare('
362
363
364
				SELECT `owncloud_name`
				FROM `'.$table.'`
				WHERE `directory_uuid` = ?
365
366
367
368
			');
			$component = $query->execute(array($uuid))->fetchOne();
			if($component) {
				$query = \OCP\DB::prepare('
369
370
371
					UPDATE `'.$table.'`
					SET `ldap_dn` = ?
					WHERE `directory_uuid` = ?
372
373
374
375
				');
				$query->execute(array($dn, $uuid));
				return $component;
			}
376
377
378
379
		} else {
			//If the UUID can't be detected something is foul.
			\OCP\Util::writeLog('user_ldap', 'Cannot determine UUID for '.$dn.'. Skipping.', \OCP\Util::INFO);
			return false;
380
381
		}

382
383
384
		if(is_null($ldapName)) {
			$ldapName = $this->readAttribute($dn, $nameAttribute);
			if(!isset($ldapName[0]) && empty($ldapName[0])) {
385
386
387
				\OCP\Util::writeLog('user_ldap', 'No or empty name for '.$dn.'.', \OCP\Util::INFO);
				return false;
			}
388
			$ldapName = $ldapName[0];
389
		}
390
391
392
393
394
395
396
397
398

		if($isUser) {
			$usernameAttribute = $this->connection->ldapExpertUsernameAttr;
			if(!emptY($usernameAttribute)) {
				$username = $this->readAttribute($dn, $usernameAttribute);
				$username = $username[0];
			} else {
				$username = $uuid;
			}
399
			$intName = $this->sanitizeUsername($username);
400
		} else {
401
			$intName = $ldapName;
402
		}
403

404
		//a new user/group! Add it only if it doesn't conflict with other backend's users or existing groups
405
		//disabling Cache is required to avoid that the new user is cached as not-existing in fooExists check
Arthur Schiwon's avatar
Arthur Schiwon committed
406
407
		//NOTE: mind, disabling cache affects only this instance! Using it
		// outside of core user management will still cache the user as non-existing.
408
409
		$originalTTL = $this->connection->ldapCacheTTL;
		$this->connection->setConfiguration(array('ldapCacheTTL' => 0));
410
411
412
		if(($isUser && !\OCP\User::userExists($intName))
			|| (!$isUser && !\OC_Group::groupExists($intName))) {
			if($this->mapComponent($dn, $intName, $isUser)) {
413
				$this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
414
				return $intName;
415
			}
416
		}
417
		$this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
418

419
420
421
		$altName = $this->createAltInternalOwnCloudName($intName, $isUser);
		if($this->mapComponent($dn, $altName, $isUser)) {
			return $altName;
422
423
		}

424
		//if everything else did not help..
425
		\OCP\Util::writeLog('user_ldap', 'Could not create unique name for '.$dn.'.', \OCP\Util::INFO);
426
		return false;
427
428
429
	}

	/**
430
	 * gives back the user names as they are used ownClod internally
431
432
	 * @param array $ldapUsers an array with the ldap Users result in style of array ( array ('dn' => foo, 'uid' => bar), ... )
	 * @return array an array with the user names to use in ownCloud
433
434
435
	 *
	 * gives back the user names as they are used ownClod internally
	 */
436
437
	public function ownCloudUserNames($ldapUsers) {
		return $this->ldap2ownCloudNames($ldapUsers, true);
438
439
440
	}

	/**
441
	 * gives back the group names as they are used ownClod internally
442
443
	 * @param array $ldapGroups an array with the ldap Groups result in style of array ( array ('dn' => foo, 'cn' => bar), ... )
	 * @return array an array with the group names to use in ownCloud
444
445
446
	 *
	 * gives back the group names as they are used ownClod internally
	 */
447
448
	public function ownCloudGroupNames($ldapGroups) {
		return $this->ldap2ownCloudNames($ldapGroups, false);
449
450
	}

451
	/**
Robin McCorkell's avatar
Robin McCorkell committed
452
	 * @param string $dn
453
454
	 * @return bool|string
	 */
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
	private function findMappedUser($dn) {
		static $query = null;
		if(is_null($query)) {
			$query = \OCP\DB::prepare('
				SELECT `owncloud_name`
				FROM `'.$this->getMapTable(true).'`
				WHERE `ldap_dn` = ?'
			);
		}
		$res = $query->execute(array($dn))->fetchOne();
		if($res) {
			return  $res;
		}
		return false;
	}

471
	/**
Robin McCorkell's avatar
Robin McCorkell committed
472
	 * @param string $dn
473
474
	 * @return bool|string
	 */
475
	private function findMappedGroup($dn) {
Bart Visscher's avatar
Bart Visscher committed
476
		static $query = null;
477
478
		if(is_null($query)) {
			$query = \OCP\DB::prepare('
Bart Visscher's avatar
Bart Visscher committed
479
480
481
482
					SELECT `owncloud_name`
					FROM `'.$this->getMapTable(false).'`
					WHERE `ldap_dn` = ?'
			);
483
		}
Bart Visscher's avatar
Bart Visscher committed
484
		$res = $query->execute(array($dn))->fetchOne();
485
		if($res) {
Bart Visscher's avatar
Bart Visscher committed
486
487
			return  $res;
		}
488
		return false;
Bart Visscher's avatar
Bart Visscher committed
489
	}
490

491
	/**
Lukas Reschke's avatar
Lukas Reschke committed
492
	 * @param array $ldapObjects
493
494
	 * @param bool $isUsers
	 * @return array
495
	 */
496
	private function ldap2ownCloudNames($ldapObjects, $isUsers) {
497
		if($isUsers) {
498
			$nameAttribute = $this->connection->ldapUserDisplayName;
499
		} else {
500
			$nameAttribute = $this->connection->ldapGroupDisplayName;
501
502
503
504
		}
		$ownCloudNames = array();

		foreach($ldapObjects as $ldapObject) {
505
			$nameByLDAP = isset($ldapObject[$nameAttribute]) ? $ldapObject[$nameAttribute] : null;
506
507
508
			$ocName = $this->dn2ocname($ldapObject['dn'], $nameByLDAP, $isUsers);
			if($ocName) {
				$ownCloudNames[] = $ocName;
509
510
511
				if($isUsers) {
					//cache the user names so it does not need to be retrieved
					//again later (e.g. sharing dialogue).
Arthur Schiwon's avatar
Arthur Schiwon committed
512
					$this->cacheUserExists($ocName);
513
					$this->cacheUserDisplayName($ocName, $nameByLDAP);
514
				}
515
			}
516
			continue;
517
518
519
520
		}
		return $ownCloudNames;
	}

Arthur Schiwon's avatar
Arthur Schiwon committed
521
522
523
524
525
526
527
528
	/**
	 * caches a user as existing
	 * @param string $ocName the internal ownCloud username
	 */
	public function cacheUserExists($ocName) {
		$this->connection->writeToCache('userExists'.$ocName, true);
	}

529
	/**
530
	 * caches the user display name
531
532
	 * @param string $ocName the internal ownCloud username
	 * @param string $displayName the display name
533
	 */
534
	public function cacheUserDisplayName($ocName, $displayName) {
535
		$cacheKeyTrunk = 'getDisplayName';
536
		$this->connection->writeToCache($cacheKeyTrunk.$ocName, $displayName);
537
538
	}

539
	/**
540
	 * creates a unique name for internal ownCloud use for users. Don't call it directly.
541
542
	 * @param string $name the display name of the object
	 * @return string with with the name to use in ownCloud or false if unsuccessful
543
544
545
546
547
548
549
550
551
	 *
	 * Instead of using this method directly, call
	 * createAltInternalOwnCloudName($name, true)
	 */
	private function _createAltInternalOwnCloudNameForUsers($name) {
		$attempts = 0;
		//while loop is just a precaution. If a name is not generated within
		//20 attempts, something else is very wrong. Avoids infinite loop.
		while($attempts < 20){
552
553
			$altName = $name . '_' . rand(1000,9999);
			if(!\OCP\User::userExists($altName)) {
554
555
556
557
558
559
560
561
				return $altName;
			}
			$attempts++;
		}
		return false;
	}

	/**
562
	 * creates a unique name for internal ownCloud use for groups. Don't call it directly.
563
564
	 * @param string $name the display name of the object
	 * @return string with with the name to use in ownCloud or false if unsuccessful.
565
566
567
	 *
	 * Instead of using this method directly, call
	 * createAltInternalOwnCloudName($name, false)
568
	 *
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
	 * Group names are also used as display names, so we do a sequential
	 * numbering, e.g. Developers_42 when there are 41 other groups called
	 * "Developers"
	 */
	private function _createAltInternalOwnCloudNameForGroups($name) {
		$query = \OCP\DB::prepare('
			SELECT `owncloud_name`
			FROM `'.$this->getMapTable(false).'`
			WHERE `owncloud_name` LIKE ?
		');

		$usedNames = array();
		$res = $query->execute(array($name.'_%'));
		while($row = $res->fetchRow()) {
			$usedNames[] = $row['owncloud_name'];
		}
585
		if(!($usedNames) || count($usedNames) === 0) {
586
587
588
			$lastNo = 1; //will become name_2
		} else {
			natsort($usedNames);
589
590
			$lastName = array_pop($usedNames);
			$lastNo = intval(substr($lastName, strrpos($lastName, '_') + 1));
591
592
593
594
595
596
		}
		$altName = $name.'_'.strval($lastNo+1);
		unset($usedNames);

		$attempts = 1;
		while($attempts < 21){
597
598
599
			// Check to be really sure it is unique
			// while loop is just a precaution. If a name is not generated within
			// 20 attempts, something else is very wrong. Avoids infinite loop.
600
601
602
			if(!\OC_Group::groupExists($altName)) {
				return $altName;
			}
Arthur Schiwon's avatar
Arthur Schiwon committed
603
			$altName = $name . '_' . ($lastNo + $attempts);
604
605
606
607
608
609
			$attempts++;
		}
		return false;
	}

	/**
610
	 * creates a unique name for internal ownCloud use.
611
	 * @param string $name the display name of the object
612
	 * @param boolean $isUser whether name should be created for a user (true) or a group (false)
613
	 * @return string with with the name to use in ownCloud or false if unsuccessful
614
	 */
615
616
617
618
619
620
621
622
623
624
625
	private function createAltInternalOwnCloudName($name, $isUser) {
		$originalTTL = $this->connection->ldapCacheTTL;
		$this->connection->setConfiguration(array('ldapCacheTTL' => 0));
		if($isUser) {
			$altName = $this->_createAltInternalOwnCloudNameForUsers($name);
		} else {
			$altName = $this->_createAltInternalOwnCloudNameForGroups($name);
		}
		$this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));

		return $altName;
626
627
628
	}

	/**
629
	 * retrieves all known groups from the mappings table
630
	 * @return array with the results
631
632
633
	 *
	 * retrieves all known groups from the mappings table
	 */
634
635
	private function mappedGroups() {
		return $this->mappedComponents(false);
636
637
638
	}

	/**
639
	 * retrieves all known users from the mappings table
640
	 * @return array with the results
641
642
643
	 *
	 * retrieves all known users from the mappings table
	 */
644
645
	private function mappedUsers() {
		return $this->mappedComponents(true);
646
647
	}

648
649
	/**
	 * @param boolean $isUsers
Arthur Schiwon's avatar
Arthur Schiwon committed
650
	 * @return array
651
	 */
652
653
	private function mappedComponents($isUsers) {
		$table = $this->getMapTable($isUsers);
654

655
		$query = \OCP\DB::prepare('
656
657
			SELECT `ldap_dn`, `owncloud_name`
			FROM `'. $table . '`'
658
659
660
661
662
663
		);

		return $query->execute()->fetchAll();
	}

	/**
664
	 * inserts a new user or group into the mappings table
665
666
667
668
	 * @param string $dn the record in question
	 * @param string $ocName the name to use in ownCloud
	 * @param bool $isUser is it a user or a group?
	 * @return bool true on success, false otherwise
669
670
671
	 *
	 * inserts a new user or group into the mappings table
	 */
672
	private function mapComponent($dn, $ocName, $isUser = true) {
673
		$table = $this->getMapTable($isUser);
674

675
		$sqlAdjustment = '';
676
		$dbType = \OCP\Config::getSystemValue('dbtype');
677
		if($dbType === 'mysql' || $dbType == 'oci') {
678
			$sqlAdjustment = 'FROM DUAL';
Arthur Schiwon's avatar
Arthur Schiwon committed
679
680
		}

681
		$insert = \OCP\DB::prepare('
682
			INSERT INTO `'.$table.'` (`ldap_dn`, `owncloud_name`, `directory_uuid`)
683
				SELECT ?,?,?
684
685
686
				'.$sqlAdjustment.'
				WHERE NOT EXISTS (
					SELECT 1
687
688
689
					FROM `'.$table.'`
					WHERE `ldap_dn` = ?
						OR `owncloud_name` = ?)
690
691
		');

692
		//feed the DB
693
		$insRows = $insert->execute(array($dn, $ocName,
694
										  $this->getUUID($dn, $isUser), $dn,
695
										  $ocName));
696

697
		if(\OCP\DB::isError($insRows)) {
698
699
700
			return false;
		}

701
		if($insRows === 0) {
702
703
704
			return false;
		}

705
706
707
		if($isUser) {
			//make sure that email address is retrieved prior to login, so user
			//will be notified when something is shared with him
Arthur Schiwon's avatar
Arthur Schiwon committed
708
			$this->userManager->get($ocName)->update();
709
710
		}

711
		return true;
712
713
	}

714
	/**
Robin McCorkell's avatar
Robin McCorkell committed
715
	 * @param string $filter
Robin McCorkell's avatar
Robin McCorkell committed
716
	 * @param string|string[] $attr
717
718
719
	 * @param int $limit
	 * @param int $offset
	 * @return array
720
	 */
721
722
	public function fetchListOfUsers($filter, $attr, $limit = null, $offset = null) {
		return $this->fetchList($this->searchUsers($filter, $attr, $limit, $offset), (count($attr) > 1));
723
724
	}

725
	/**
Robin McCorkell's avatar
Robin McCorkell committed
726
	 * @param string $filter
Robin McCorkell's avatar
Robin McCorkell committed
727
	 * @param string|string[] $attr
728
729
730
	 * @param int $limit
	 * @param int $offset
	 * @return array
731
	 */
732
733
	public function fetchListOfGroups($filter, $attr, $limit = null, $offset = null) {
		return $this->fetchList($this->searchGroups($filter, $attr, $limit, $offset), (count($attr) > 1));
734
735
	}

736
	/**
Robin McCorkell's avatar
Robin McCorkell committed
737
	 * @param array $list
738
739
	 * @param bool $manyAttributes
	 * @return array
740
	 */
741
	private function fetchList($list, $manyAttributes) {
742
743
744
745
746
747
748
749
750
751
752
753
		if(is_array($list)) {
			if($manyAttributes) {
				return $list;
			} else {
				return array_unique($list, SORT_LOCALE_STRING);
			}
		}

		//error cause actually, maybe throw an exception in future.
		return array();
	}

754
	/**
755
	 * executes an LDAP search, optimized for Users
756
	 * @param string $filter the LDAP filter for the search
Robin McCorkell's avatar
Robin McCorkell committed
757
	 * @param string|string[] $attr optional, when a certain attribute shall be filtered out
758
759
	 * @param integer $limit
	 * @param integer $offset
760
	 * @return array with the search result
761
762
763
	 *
	 * Executes an LDAP search
	 */
764
765
	public function searchUsers($filter, $attr = null, $limit = null, $offset = null) {
		return $this->search($filter, $this->connection->ldapBaseUsers, $attr, $limit, $offset);
766
767
	}

768
769
	/**
	 * @param string $filter
Robin McCorkell's avatar
Robin McCorkell committed
770
	 * @param string|string[] $attr
771
772
773
	 * @param int $limit
	 * @param int $offset
	 * @return false|int
774
	 */
775
	public function countUsers($filter, $attr = array('dn'), $limit = null, $offset = null) {
776
		return $this->count($filter, $this->connection->ldapBaseUsers, $attr, $limit, $offset);
777
778
	}

779
	/**
780
	 * executes an LDAP search, optimized for Groups
781
	 * @param string $filter the LDAP filter for the search
Robin McCorkell's avatar
Robin McCorkell committed
782
	 * @param string|string[] $attr optional, when a certain attribute shall be filtered out
783
784
	 * @param integer $limit
	 * @param integer $offset
785
	 * @return array with the search result
786
787
788
	 *
	 * Executes an LDAP search
	 */
789
790
	public function searchGroups($filter, $attr = null, $limit = null, $offset = null) {
		return $this->search($filter, $this->connection->ldapBaseGroups, $attr, $limit, $offset);
791
792
	}

Arthur Schiwon's avatar
Arthur Schiwon committed
793
	/**
794
795
796
797
798
799
800
801
802
803
804
805
	 * returns the number of available groups
	 * @param string $filter the LDAP search filter
	 * @param string[] $attr optional
	 * @param int|null $limit
	 * @param int|null $offset
	 * @return int|bool
	 */
	public function countGroups($filter, $attr = array('dn'), $limit = null, $offset = null) {
		return $this->count($filter, $this->connection->ldapBaseGroups, $attr, $limit, $offset);
	}

	/**
806
	 * retrieved. Results will according to the order in the array.
807
808
809
	 * @param int $limit optional, maximum results to be counted
	 * @param int $offset optional, a starting point
	 * @return array|false array with the search result as first value and pagedSearchOK as
810
	 * second | false if not successful
Arthur Schiwon's avatar
Arthur Schiwon committed
811
	 */
812
	private function executeSearch($filter, $base, &$attr = null, $limit = null, $offset = null) {
813
		if(!is_null($attr) && !is_array($attr)) {
Arthur Schiwon's avatar
Arthur Schiwon committed
814
			$attr = array(mb_strtolower($attr, 'UTF-8'));
815
		}
816

817
		// See if we have a resource, in case not cancel with message
818
819
		$cr = $this->connection->getConnectionResource();
		if(!$this->ldap->isResource($cr)) {
820
821
			// Seems like we didn't find any resource.
			// Return an empty array just like before.
822
			\OCP\Util::writeLog('user_ldap', 'Could not search, because resource is missing.', \OCP\Util::DEBUG);
823
			return false;
824
		}
825

826
		//check whether paged search should be attempted
827
		$pagedSearchOK = $this->initPagedSearch($filter, $base, $attr, intval($limit), $offset);
828

829
		$linkResources = array_pad(array(), count($base), $cr);
830
		$sr = $this->ldap->search($linkResources, $base, $filter, $attr);
831
		$error = $this->ldap->errno($cr);
832
		if(!is_array($sr) || $error !== 0) {
Bart Visscher's avatar
Bart Visscher committed
833
			\OCP\Util::writeLog('user_ldap',
834
835
				'Error when searching: '.$this->ldap->error($cr).
					' code '.$this->ldap->errno($cr),
Bart Visscher's avatar
Bart Visscher committed
836
				\OCP\Util::ERROR);
837
			\OCP\Util::writeLog('user_ldap', 'Attempt for Paging?  '.print_r($pagedSearchOK, true), \OCP\Util::ERROR);
838
			return false;
839
		}
840

841
842
		return array($sr, $pagedSearchOK);
	}
843

844
	/**
845
	 * processes an LDAP paged search operation
846
847
848
849
850
851
852
853
	 * @param array $sr the array containing the LDAP search resources
	 * @param string $filter the LDAP filter for the search
	 * @param array $base an array containing the LDAP subtree(s) that shall be searched
	 * @param int $iFoundItems number of results in the search operation
	 * @param int $limit maximum results to be counted
	 * @param int $offset a starting point
	 * @param bool $pagedSearchOK whether a paged search has been executed
	 * @param bool $skipHandling required for paged search when cookies to
854
	 * prior results need to be gained
855
	 * @return array|false array with the search result as first value and pagedSearchOK as
856
857
858
	 * second | false if not successful
	 */
	private function processPagedSearchStatus($sr, $filter, $base, $iFoundItems, $limit, $offset, $pagedSearchOK, $skipHandling) {
859
		if($pagedSearchOK) {
860
			$cr = $this->connection->getConnectionResource();
861
862
			foreach($sr as $key => $res) {
				$cookie = null;
863
				if($this->ldap->controlPagedResultResponse($cr, $res, $cookie)) {
864
865
866
867
					$this->setPagedResultCookie($base[$key], $filter, $limit, $offset, $cookie);
				}
			}

868
869
870
871
			//browsing through prior pages to get the cookie for the new one
			if($skipHandling) {
				return;
			}
Bart Visscher's avatar
Bart Visscher committed
872
873
874
			// if count is bigger, then the server does not support
			// paged search. Instead, he did a normal search. We set a
			// flag here, so the callee knows how to deal with it.
875
			if($iFoundItems <= $limit) {
876
877
878
				$this->pagedSearchedSuccessful = true;
			}
		} else {
879
			if(!is_null($limit)) {
880
				\OCP\Util::writeLog('user_ldap', 'Paged search was not available', \OCP\Util::INFO);
881
			}
882
		}
883
884
885
	}

	/**
886
	 * executes an LDAP search, but counts the results only
887
	 * @param string $filter the LDAP filter for the search
888
	 * @param array $base an array containing the LDAP subtree(s) that shall be searched
Robin McCorkell's avatar
Robin McCorkell committed
889
	 * @param string|string[] $attr optional, array, one or more attributes that shall be
890
	 * retrieved. Results will according to the order in the array.
891
892
893
	 * @param int $limit optional, maximum results to be counted
	 * @param int $offset optional, a starting point
	 * @param bool $skipHandling indicates whether the pages search operation is