Completed
Push — master ( c316ed...9d6353 )
by Blizzz
30:19
created
apps/user_ldap/lib/Mapping/UserMapping.php 1 patch
Indentation   +40 added lines, -40 removed lines patch added patch discarded remove patch
@@ -22,48 +22,48 @@
 block discarded – undo
22 22
  */
23 23
 class UserMapping extends AbstractMapping {
24 24
 
25
-	protected const PROV_API_REGEX = '/\/ocs\/v[1-9].php\/cloud\/(groups|users)/';
25
+    protected const PROV_API_REGEX = '/\/ocs\/v[1-9].php\/cloud\/(groups|users)/';
26 26
 
27
-	public function __construct(
28
-		IDBConnection $dbc,
29
-		ICacheFactory $cacheFactory,
30
-		IAppConfig $config,
31
-		bool $isCLI,
32
-		private IAssertion $assertion,
33
-	) {
34
-		parent::__construct($dbc, $cacheFactory, $config, $isCLI);
35
-	}
27
+    public function __construct(
28
+        IDBConnection $dbc,
29
+        ICacheFactory $cacheFactory,
30
+        IAppConfig $config,
31
+        bool $isCLI,
32
+        private IAssertion $assertion,
33
+    ) {
34
+        parent::__construct($dbc, $cacheFactory, $config, $isCLI);
35
+    }
36 36
 
37
-	/**
38
-	 * @throws HintException
39
-	 */
40
-	public function map($fdn, $name, $uuid): bool {
41
-		try {
42
-			$this->assertion->createUserIsLegit();
43
-		} catch (HintException $e) {
44
-			static $isProvisioningApi = null;
37
+    /**
38
+     * @throws HintException
39
+     */
40
+    public function map($fdn, $name, $uuid): bool {
41
+        try {
42
+            $this->assertion->createUserIsLegit();
43
+        } catch (HintException $e) {
44
+            static $isProvisioningApi = null;
45 45
 
46
-			if ($isProvisioningApi === null) {
47
-				$request = Server::get(IRequest::class);
48
-				$isProvisioningApi = \preg_match(self::PROV_API_REGEX, $request->getRequestUri()) === 1;
49
-			}
50
-			if ($isProvisioningApi) {
51
-				// only throw when prov API is being used, since functionality
52
-				// should not break for end users (e.g. when sharing).
53
-				// On direct API usage, e.g. on users page, this is desired.
54
-				throw $e;
55
-			}
56
-			return false;
57
-		}
58
-		return parent::map($fdn, $name, $uuid);
59
-	}
46
+            if ($isProvisioningApi === null) {
47
+                $request = Server::get(IRequest::class);
48
+                $isProvisioningApi = \preg_match(self::PROV_API_REGEX, $request->getRequestUri()) === 1;
49
+            }
50
+            if ($isProvisioningApi) {
51
+                // only throw when prov API is being used, since functionality
52
+                // should not break for end users (e.g. when sharing).
53
+                // On direct API usage, e.g. on users page, this is desired.
54
+                throw $e;
55
+            }
56
+            return false;
57
+        }
58
+        return parent::map($fdn, $name, $uuid);
59
+    }
60 60
 
61
-	/**
62
-	 * returns the DB table name which holds the mappings
63
-	 * @return string
64
-	 */
65
-	protected function getTableName(bool $includePrefix = true) {
66
-		$p = $includePrefix ? '*PREFIX*' : '';
67
-		return $p . 'ldap_user_mapping';
68
-	}
61
+    /**
62
+     * returns the DB table name which holds the mappings
63
+     * @return string
64
+     */
65
+    protected function getTableName(bool $includePrefix = true) {
66
+        $p = $includePrefix ? '*PREFIX*' : '';
67
+        return $p . 'ldap_user_mapping';
68
+    }
69 69
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Mapping/AbstractMapping.php 2 patches
Indentation   +485 added lines, -485 removed lines patch added patch discarded remove patch
@@ -23,507 +23,507 @@
 block discarded – undo
23 23
  * @package OCA\User_LDAP\Mapping
24 24
  */
25 25
 abstract class AbstractMapping {
26
-	/**
27
-	 * returns the DB table name which holds the mappings
28
-	 *
29
-	 * @return string
30
-	 */
31
-	abstract protected function getTableName(bool $includePrefix = true);
32
-
33
-	/**
34
-	 * A month worth of cache time for as good as never changing mapping data.
35
-	 * Implemented when it was found that name-to-DN lookups are quite frequent.
36
-	 */
37
-	protected const LOCAL_CACHE_TTL = 2592000;
38
-
39
-	/**
40
-	 * A week worth of cache time for rarely changing user count data.
41
-	 */
42
-	protected const LOCAL_USER_COUNT_TTL = 604800;
43
-
44
-	/**
45
-	 * By default, the local cache is only used up to a certain amount of objects.
46
-	 * This constant holds this number. The amount of entries would amount up to
47
-	 * 1 MiB (worst case) per mappings table.
48
-	 * Setting `use_local_mapping_cache` for `user_ldap` to `yes` or `no`
49
-	 * deliberately enables or disables this mechanism.
50
-	 */
51
-	protected const LOCAL_CACHE_OBJECT_THRESHOLD = 2000;
52
-
53
-	protected ?ICache $localNameToDnCache = null;
54
-
55
-	/** @var array caches Names (value) by DN (key) */
56
-	protected array $cache = [];
57
-
58
-	/**
59
-	 * @param IDBConnection $dbc
60
-	 */
61
-	public function __construct(
62
-		protected IDBConnection $dbc,
63
-		protected ICacheFactory $cacheFactory,
64
-		protected IAppConfig $config,
65
-		protected bool $isCLI,
66
-	) {
67
-		$this->initLocalCache();
68
-	}
69
-
70
-	protected function initLocalCache(): void {
71
-		if ($this->isCLI || !$this->cacheFactory->isLocalCacheAvailable()) {
72
-			return;
73
-		}
74
-
75
-		$useLocalCache = $this->config->getValueString('user_ldap', 'use_local_mapping_cache', 'auto', false);
76
-		if ($useLocalCache !== 'yes' && $useLocalCache !== 'auto') {
77
-			return;
78
-		}
79
-
80
-		$section = \str_contains($this->getTableName(), 'user') ? 'u/' : 'g/';
81
-		$this->localNameToDnCache = $this->cacheFactory->createLocal('ldap/map/' . $section);
82
-
83
-		// We use the cache as well to store whether it shall be used. If the
84
-		// answer was no, we unset it again.
85
-		if ($useLocalCache === 'auto' && !$this->useCacheByUserCount()) {
86
-			$this->localNameToDnCache = null;
87
-		}
88
-	}
89
-
90
-	protected function useCacheByUserCount(): bool {
91
-		$use = $this->localNameToDnCache->get('use');
92
-		if ($use !== null) {
93
-			return $use;
94
-		}
95
-
96
-		$qb = $this->dbc->getQueryBuilder();
97
-		$q = $qb->selectAlias($qb->createFunction('COUNT(owncloud_name)'), 'count')
98
-			->from($this->getTableName());
99
-		$q->setMaxResults(self::LOCAL_CACHE_OBJECT_THRESHOLD + 1);
100
-		$result = $q->executeQuery();
101
-		$row = $result->fetch();
102
-		$result->closeCursor();
103
-
104
-		$use = (int)$row['count'] <= self::LOCAL_CACHE_OBJECT_THRESHOLD;
105
-		$this->localNameToDnCache->set('use', $use, self::LOCAL_USER_COUNT_TTL);
106
-		return $use;
107
-	}
108
-
109
-	/**
110
-	 * checks whether a provided string represents an existing table col
111
-	 *
112
-	 * @param string $col
113
-	 * @return bool
114
-	 */
115
-	public function isColNameValid($col) {
116
-		switch ($col) {
117
-			case 'ldap_dn':
118
-			case 'ldap_dn_hash':
119
-			case 'owncloud_name':
120
-			case 'directory_uuid':
121
-				return true;
122
-			default:
123
-				return false;
124
-		}
125
-	}
126
-
127
-	/**
128
-	 * Gets the value of one column based on a provided value of another column
129
-	 *
130
-	 * @param string $fetchCol
131
-	 * @param string $compareCol
132
-	 * @param string $search
133
-	 * @return string|false
134
-	 * @throws \Exception
135
-	 */
136
-	protected function getXbyY($fetchCol, $compareCol, $search) {
137
-		if (!$this->isColNameValid($fetchCol)) {
138
-			//this is used internally only, but we don't want to risk
139
-			//having SQL injection at all.
140
-			throw new \Exception('Invalid Column Name');
141
-		}
142
-		$qb = $this->dbc->getQueryBuilder();
143
-		$qb->select($fetchCol)
144
-			->from($this->getTableName())
145
-			->where($qb->expr()->eq($compareCol, $qb->createNamedParameter($search)));
146
-
147
-		try {
148
-			$res = $qb->executeQuery();
149
-			$data = $res->fetchOne();
150
-			$res->closeCursor();
151
-			return $data;
152
-		} catch (Exception $e) {
153
-			return false;
154
-		}
155
-	}
156
-
157
-	/**
158
-	 * Performs a DELETE or UPDATE query to the database.
159
-	 *
160
-	 * @param IPreparedStatement $statement
161
-	 * @param array $parameters
162
-	 * @return bool true if at least one row was modified, false otherwise
163
-	 */
164
-	protected function modify(IPreparedStatement $statement, $parameters) {
165
-		try {
166
-			$result = $statement->execute($parameters);
167
-			$updated = $result->rowCount() > 0;
168
-			$result->closeCursor();
169
-			return $updated;
170
-		} catch (Exception $e) {
171
-			return false;
172
-		}
173
-	}
174
-
175
-	/**
176
-	 * Gets the LDAP DN based on the provided name.
177
-	 */
178
-	public function getDNByName(string $name): string|false {
179
-		$dn = array_search($name, $this->cache, true);
180
-		if ($dn === false) {
181
-			$dn = $this->localNameToDnCache?->get($name);
182
-			if ($dn === null) {
183
-				$dn = $this->getXbyY('ldap_dn', 'owncloud_name', $name);
184
-				if ($dn !== false) {
185
-					$this->cache[$dn] = $name;
186
-				}
187
-				$this->localNameToDnCache?->set($name, $dn, self::LOCAL_CACHE_TTL);
188
-			}
189
-		}
190
-		return $dn ?? false;
191
-	}
192
-
193
-	/**
194
-	 * Updates the DN based on the given UUID
195
-	 *
196
-	 * @param string $fdn
197
-	 * @param string $uuid
198
-	 * @return bool
199
-	 */
200
-	public function setDNbyUUID($fdn, $uuid) {
201
-		$oldDn = $this->getDnByUUID($uuid);
202
-		$statement = $this->dbc->prepare('
26
+    /**
27
+     * returns the DB table name which holds the mappings
28
+     *
29
+     * @return string
30
+     */
31
+    abstract protected function getTableName(bool $includePrefix = true);
32
+
33
+    /**
34
+     * A month worth of cache time for as good as never changing mapping data.
35
+     * Implemented when it was found that name-to-DN lookups are quite frequent.
36
+     */
37
+    protected const LOCAL_CACHE_TTL = 2592000;
38
+
39
+    /**
40
+     * A week worth of cache time for rarely changing user count data.
41
+     */
42
+    protected const LOCAL_USER_COUNT_TTL = 604800;
43
+
44
+    /**
45
+     * By default, the local cache is only used up to a certain amount of objects.
46
+     * This constant holds this number. The amount of entries would amount up to
47
+     * 1 MiB (worst case) per mappings table.
48
+     * Setting `use_local_mapping_cache` for `user_ldap` to `yes` or `no`
49
+     * deliberately enables or disables this mechanism.
50
+     */
51
+    protected const LOCAL_CACHE_OBJECT_THRESHOLD = 2000;
52
+
53
+    protected ?ICache $localNameToDnCache = null;
54
+
55
+    /** @var array caches Names (value) by DN (key) */
56
+    protected array $cache = [];
57
+
58
+    /**
59
+     * @param IDBConnection $dbc
60
+     */
61
+    public function __construct(
62
+        protected IDBConnection $dbc,
63
+        protected ICacheFactory $cacheFactory,
64
+        protected IAppConfig $config,
65
+        protected bool $isCLI,
66
+    ) {
67
+        $this->initLocalCache();
68
+    }
69
+
70
+    protected function initLocalCache(): void {
71
+        if ($this->isCLI || !$this->cacheFactory->isLocalCacheAvailable()) {
72
+            return;
73
+        }
74
+
75
+        $useLocalCache = $this->config->getValueString('user_ldap', 'use_local_mapping_cache', 'auto', false);
76
+        if ($useLocalCache !== 'yes' && $useLocalCache !== 'auto') {
77
+            return;
78
+        }
79
+
80
+        $section = \str_contains($this->getTableName(), 'user') ? 'u/' : 'g/';
81
+        $this->localNameToDnCache = $this->cacheFactory->createLocal('ldap/map/' . $section);
82
+
83
+        // We use the cache as well to store whether it shall be used. If the
84
+        // answer was no, we unset it again.
85
+        if ($useLocalCache === 'auto' && !$this->useCacheByUserCount()) {
86
+            $this->localNameToDnCache = null;
87
+        }
88
+    }
89
+
90
+    protected function useCacheByUserCount(): bool {
91
+        $use = $this->localNameToDnCache->get('use');
92
+        if ($use !== null) {
93
+            return $use;
94
+        }
95
+
96
+        $qb = $this->dbc->getQueryBuilder();
97
+        $q = $qb->selectAlias($qb->createFunction('COUNT(owncloud_name)'), 'count')
98
+            ->from($this->getTableName());
99
+        $q->setMaxResults(self::LOCAL_CACHE_OBJECT_THRESHOLD + 1);
100
+        $result = $q->executeQuery();
101
+        $row = $result->fetch();
102
+        $result->closeCursor();
103
+
104
+        $use = (int)$row['count'] <= self::LOCAL_CACHE_OBJECT_THRESHOLD;
105
+        $this->localNameToDnCache->set('use', $use, self::LOCAL_USER_COUNT_TTL);
106
+        return $use;
107
+    }
108
+
109
+    /**
110
+     * checks whether a provided string represents an existing table col
111
+     *
112
+     * @param string $col
113
+     * @return bool
114
+     */
115
+    public function isColNameValid($col) {
116
+        switch ($col) {
117
+            case 'ldap_dn':
118
+            case 'ldap_dn_hash':
119
+            case 'owncloud_name':
120
+            case 'directory_uuid':
121
+                return true;
122
+            default:
123
+                return false;
124
+        }
125
+    }
126
+
127
+    /**
128
+     * Gets the value of one column based on a provided value of another column
129
+     *
130
+     * @param string $fetchCol
131
+     * @param string $compareCol
132
+     * @param string $search
133
+     * @return string|false
134
+     * @throws \Exception
135
+     */
136
+    protected function getXbyY($fetchCol, $compareCol, $search) {
137
+        if (!$this->isColNameValid($fetchCol)) {
138
+            //this is used internally only, but we don't want to risk
139
+            //having SQL injection at all.
140
+            throw new \Exception('Invalid Column Name');
141
+        }
142
+        $qb = $this->dbc->getQueryBuilder();
143
+        $qb->select($fetchCol)
144
+            ->from($this->getTableName())
145
+            ->where($qb->expr()->eq($compareCol, $qb->createNamedParameter($search)));
146
+
147
+        try {
148
+            $res = $qb->executeQuery();
149
+            $data = $res->fetchOne();
150
+            $res->closeCursor();
151
+            return $data;
152
+        } catch (Exception $e) {
153
+            return false;
154
+        }
155
+    }
156
+
157
+    /**
158
+     * Performs a DELETE or UPDATE query to the database.
159
+     *
160
+     * @param IPreparedStatement $statement
161
+     * @param array $parameters
162
+     * @return bool true if at least one row was modified, false otherwise
163
+     */
164
+    protected function modify(IPreparedStatement $statement, $parameters) {
165
+        try {
166
+            $result = $statement->execute($parameters);
167
+            $updated = $result->rowCount() > 0;
168
+            $result->closeCursor();
169
+            return $updated;
170
+        } catch (Exception $e) {
171
+            return false;
172
+        }
173
+    }
174
+
175
+    /**
176
+     * Gets the LDAP DN based on the provided name.
177
+     */
178
+    public function getDNByName(string $name): string|false {
179
+        $dn = array_search($name, $this->cache, true);
180
+        if ($dn === false) {
181
+            $dn = $this->localNameToDnCache?->get($name);
182
+            if ($dn === null) {
183
+                $dn = $this->getXbyY('ldap_dn', 'owncloud_name', $name);
184
+                if ($dn !== false) {
185
+                    $this->cache[$dn] = $name;
186
+                }
187
+                $this->localNameToDnCache?->set($name, $dn, self::LOCAL_CACHE_TTL);
188
+            }
189
+        }
190
+        return $dn ?? false;
191
+    }
192
+
193
+    /**
194
+     * Updates the DN based on the given UUID
195
+     *
196
+     * @param string $fdn
197
+     * @param string $uuid
198
+     * @return bool
199
+     */
200
+    public function setDNbyUUID($fdn, $uuid) {
201
+        $oldDn = $this->getDnByUUID($uuid);
202
+        $statement = $this->dbc->prepare('
203 203
 			UPDATE `' . $this->getTableName() . '`
204 204
 			SET `ldap_dn_hash` = ?, `ldap_dn` = ?
205 205
 			WHERE `directory_uuid` = ?
206 206
 		');
207 207
 
208
-		$r = $this->modify($statement, [$this->getDNHash($fdn), $fdn, $uuid]);
209
-		if ($r) {
210
-			if (is_string($oldDn) && isset($this->cache[$oldDn])) {
211
-				$userId = $this->cache[$oldDn];
212
-			}
213
-			$userId = $userId ?? $this->getNameByUUID($uuid);
214
-			if ($userId) {
215
-				$this->cache[$fdn] = $userId;
216
-				$this->localNameToDnCache?->set($userId, $fdn, self::LOCAL_CACHE_TTL);
217
-			}
218
-			unset($this->cache[$oldDn]);
219
-		}
220
-
221
-		return $r;
222
-	}
223
-
224
-	/**
225
-	 * Updates the UUID based on the given DN
226
-	 *
227
-	 * required by Migration/UUIDFix
228
-	 *
229
-	 * @param $uuid
230
-	 * @param $fdn
231
-	 * @return bool
232
-	 */
233
-	public function setUUIDbyDN($uuid, $fdn): bool {
234
-		$statement = $this->dbc->prepare('
208
+        $r = $this->modify($statement, [$this->getDNHash($fdn), $fdn, $uuid]);
209
+        if ($r) {
210
+            if (is_string($oldDn) && isset($this->cache[$oldDn])) {
211
+                $userId = $this->cache[$oldDn];
212
+            }
213
+            $userId = $userId ?? $this->getNameByUUID($uuid);
214
+            if ($userId) {
215
+                $this->cache[$fdn] = $userId;
216
+                $this->localNameToDnCache?->set($userId, $fdn, self::LOCAL_CACHE_TTL);
217
+            }
218
+            unset($this->cache[$oldDn]);
219
+        }
220
+
221
+        return $r;
222
+    }
223
+
224
+    /**
225
+     * Updates the UUID based on the given DN
226
+     *
227
+     * required by Migration/UUIDFix
228
+     *
229
+     * @param $uuid
230
+     * @param $fdn
231
+     * @return bool
232
+     */
233
+    public function setUUIDbyDN($uuid, $fdn): bool {
234
+        $statement = $this->dbc->prepare('
235 235
 			UPDATE `' . $this->getTableName() . '`
236 236
 			SET `directory_uuid` = ?
237 237
 			WHERE `ldap_dn_hash` = ?
238 238
 		');
239 239
 
240
-		unset($this->cache[$fdn]);
241
-
242
-		return $this->modify($statement, [$uuid, $this->getDNHash($fdn)]);
243
-	}
244
-
245
-	/**
246
-	 * Get the hash to store in database column ldap_dn_hash for a given dn
247
-	 */
248
-	protected function getDNHash(string $fdn): string {
249
-		return hash('sha256', $fdn, false);
250
-	}
251
-
252
-	/**
253
-	 * Gets the name based on the provided LDAP DN.
254
-	 *
255
-	 * @param string $fdn
256
-	 * @return string|false
257
-	 */
258
-	public function getNameByDN($fdn) {
259
-		if (!isset($this->cache[$fdn])) {
260
-			$this->cache[$fdn] = $this->getXbyY('owncloud_name', 'ldap_dn_hash', $this->getDNHash($fdn));
261
-		}
262
-		return $this->cache[$fdn];
263
-	}
264
-
265
-	/**
266
-	 * @param array<string> $hashList
267
-	 */
268
-	protected function prepareListOfIdsQuery(array $hashList): IQueryBuilder {
269
-		$qb = $this->dbc->getQueryBuilder();
270
-		$qb->select('owncloud_name', 'ldap_dn_hash', 'ldap_dn')
271
-			->from($this->getTableName(false))
272
-			->where($qb->expr()->in('ldap_dn_hash', $qb->createNamedParameter($hashList, IQueryBuilder::PARAM_STR_ARRAY)));
273
-		return $qb;
274
-	}
275
-
276
-	protected function collectResultsFromListOfIdsQuery(IQueryBuilder $qb, array &$results): void {
277
-		$stmt = $qb->executeQuery();
278
-		while ($entry = $stmt->fetch(\Doctrine\DBAL\FetchMode::ASSOCIATIVE)) {
279
-			$results[$entry['ldap_dn']] = $entry['owncloud_name'];
280
-			$this->cache[$entry['ldap_dn']] = $entry['owncloud_name'];
281
-		}
282
-		$stmt->closeCursor();
283
-	}
284
-
285
-	/**
286
-	 * @param array<string> $fdns
287
-	 * @return array<string,string>
288
-	 */
289
-	public function getListOfIdsByDn(array $fdns): array {
290
-		$totalDBParamLimit = 65000;
291
-		$sliceSize = 1000;
292
-		$maxSlices = $this->dbc->getDatabaseProvider() === IDBConnection::PLATFORM_SQLITE ? 9 : $totalDBParamLimit / $sliceSize;
293
-		$results = [];
294
-
295
-		$slice = 1;
296
-		$fdns = array_map([$this, 'getDNHash'], $fdns);
297
-		$fdnsSlice = count($fdns) > $sliceSize ? array_slice($fdns, 0, $sliceSize) : $fdns;
298
-		$qb = $this->prepareListOfIdsQuery($fdnsSlice);
299
-
300
-		while (isset($fdnsSlice[999])) {
301
-			// Oracle does not allow more than 1000 values in the IN list,
302
-			// but allows slicing
303
-			$slice++;
304
-			$fdnsSlice = array_slice($fdns, $sliceSize * ($slice - 1), $sliceSize);
305
-
306
-			/** @see https://github.com/vimeo/psalm/issues/4995 */
307
-			/** @psalm-suppress TypeDoesNotContainType */
308
-			if (!isset($qb)) {
309
-				$qb = $this->prepareListOfIdsQuery($fdnsSlice);
310
-				continue;
311
-			}
312
-
313
-			if (!empty($fdnsSlice)) {
314
-				$qb->orWhere($qb->expr()->in('ldap_dn_hash', $qb->createNamedParameter($fdnsSlice, IQueryBuilder::PARAM_STR_ARRAY)));
315
-			}
316
-
317
-			if ($slice % $maxSlices === 0) {
318
-				$this->collectResultsFromListOfIdsQuery($qb, $results);
319
-				unset($qb);
320
-			}
321
-		}
322
-
323
-		if (isset($qb)) {
324
-			$this->collectResultsFromListOfIdsQuery($qb, $results);
325
-		}
326
-
327
-		return $results;
328
-	}
329
-
330
-	/**
331
-	 * Searches mapped names by the giving string in the name column
332
-	 *
333
-	 * @return string[]
334
-	 */
335
-	public function getNamesBySearch(string $search, string $prefixMatch = '', string $postfixMatch = ''): array {
336
-		$statement = $this->dbc->prepare('
240
+        unset($this->cache[$fdn]);
241
+
242
+        return $this->modify($statement, [$uuid, $this->getDNHash($fdn)]);
243
+    }
244
+
245
+    /**
246
+     * Get the hash to store in database column ldap_dn_hash for a given dn
247
+     */
248
+    protected function getDNHash(string $fdn): string {
249
+        return hash('sha256', $fdn, false);
250
+    }
251
+
252
+    /**
253
+     * Gets the name based on the provided LDAP DN.
254
+     *
255
+     * @param string $fdn
256
+     * @return string|false
257
+     */
258
+    public function getNameByDN($fdn) {
259
+        if (!isset($this->cache[$fdn])) {
260
+            $this->cache[$fdn] = $this->getXbyY('owncloud_name', 'ldap_dn_hash', $this->getDNHash($fdn));
261
+        }
262
+        return $this->cache[$fdn];
263
+    }
264
+
265
+    /**
266
+     * @param array<string> $hashList
267
+     */
268
+    protected function prepareListOfIdsQuery(array $hashList): IQueryBuilder {
269
+        $qb = $this->dbc->getQueryBuilder();
270
+        $qb->select('owncloud_name', 'ldap_dn_hash', 'ldap_dn')
271
+            ->from($this->getTableName(false))
272
+            ->where($qb->expr()->in('ldap_dn_hash', $qb->createNamedParameter($hashList, IQueryBuilder::PARAM_STR_ARRAY)));
273
+        return $qb;
274
+    }
275
+
276
+    protected function collectResultsFromListOfIdsQuery(IQueryBuilder $qb, array &$results): void {
277
+        $stmt = $qb->executeQuery();
278
+        while ($entry = $stmt->fetch(\Doctrine\DBAL\FetchMode::ASSOCIATIVE)) {
279
+            $results[$entry['ldap_dn']] = $entry['owncloud_name'];
280
+            $this->cache[$entry['ldap_dn']] = $entry['owncloud_name'];
281
+        }
282
+        $stmt->closeCursor();
283
+    }
284
+
285
+    /**
286
+     * @param array<string> $fdns
287
+     * @return array<string,string>
288
+     */
289
+    public function getListOfIdsByDn(array $fdns): array {
290
+        $totalDBParamLimit = 65000;
291
+        $sliceSize = 1000;
292
+        $maxSlices = $this->dbc->getDatabaseProvider() === IDBConnection::PLATFORM_SQLITE ? 9 : $totalDBParamLimit / $sliceSize;
293
+        $results = [];
294
+
295
+        $slice = 1;
296
+        $fdns = array_map([$this, 'getDNHash'], $fdns);
297
+        $fdnsSlice = count($fdns) > $sliceSize ? array_slice($fdns, 0, $sliceSize) : $fdns;
298
+        $qb = $this->prepareListOfIdsQuery($fdnsSlice);
299
+
300
+        while (isset($fdnsSlice[999])) {
301
+            // Oracle does not allow more than 1000 values in the IN list,
302
+            // but allows slicing
303
+            $slice++;
304
+            $fdnsSlice = array_slice($fdns, $sliceSize * ($slice - 1), $sliceSize);
305
+
306
+            /** @see https://github.com/vimeo/psalm/issues/4995 */
307
+            /** @psalm-suppress TypeDoesNotContainType */
308
+            if (!isset($qb)) {
309
+                $qb = $this->prepareListOfIdsQuery($fdnsSlice);
310
+                continue;
311
+            }
312
+
313
+            if (!empty($fdnsSlice)) {
314
+                $qb->orWhere($qb->expr()->in('ldap_dn_hash', $qb->createNamedParameter($fdnsSlice, IQueryBuilder::PARAM_STR_ARRAY)));
315
+            }
316
+
317
+            if ($slice % $maxSlices === 0) {
318
+                $this->collectResultsFromListOfIdsQuery($qb, $results);
319
+                unset($qb);
320
+            }
321
+        }
322
+
323
+        if (isset($qb)) {
324
+            $this->collectResultsFromListOfIdsQuery($qb, $results);
325
+        }
326
+
327
+        return $results;
328
+    }
329
+
330
+    /**
331
+     * Searches mapped names by the giving string in the name column
332
+     *
333
+     * @return string[]
334
+     */
335
+    public function getNamesBySearch(string $search, string $prefixMatch = '', string $postfixMatch = ''): array {
336
+        $statement = $this->dbc->prepare('
337 337
 			SELECT `owncloud_name`
338 338
 			FROM `' . $this->getTableName() . '`
339 339
 			WHERE `owncloud_name` LIKE ?
340 340
 		');
341 341
 
342
-		try {
343
-			$res = $statement->execute([$prefixMatch . $this->dbc->escapeLikeParameter($search) . $postfixMatch]);
344
-		} catch (Exception $e) {
345
-			return [];
346
-		}
347
-		$names = [];
348
-		while ($row = $res->fetch()) {
349
-			$names[] = $row['owncloud_name'];
350
-		}
351
-		return $names;
352
-	}
353
-
354
-	/**
355
-	 * Gets the name based on the provided LDAP UUID.
356
-	 *
357
-	 * @param string $uuid
358
-	 * @return string|false
359
-	 */
360
-	public function getNameByUUID($uuid) {
361
-		return $this->getXbyY('owncloud_name', 'directory_uuid', $uuid);
362
-	}
363
-
364
-	public function getDnByUUID($uuid) {
365
-		return $this->getXbyY('ldap_dn', 'directory_uuid', $uuid);
366
-	}
367
-
368
-	/**
369
-	 * Gets the UUID based on the provided LDAP DN
370
-	 *
371
-	 * @param string $dn
372
-	 * @return false|string
373
-	 * @throws \Exception
374
-	 */
375
-	public function getUUIDByDN($dn) {
376
-		return $this->getXbyY('directory_uuid', 'ldap_dn_hash', $this->getDNHash($dn));
377
-	}
378
-
379
-	public function getList(int $offset = 0, ?int $limit = null, bool $invalidatedOnly = false): array {
380
-		$select = $this->dbc->getQueryBuilder();
381
-		$select->selectAlias('ldap_dn', 'dn')
382
-			->selectAlias('owncloud_name', 'name')
383
-			->selectAlias('directory_uuid', 'uuid')
384
-			->from($this->getTableName())
385
-			->setMaxResults($limit)
386
-			->setFirstResult($offset);
387
-
388
-		if ($invalidatedOnly) {
389
-			$select->where($select->expr()->like('directory_uuid', $select->createNamedParameter('invalidated_%')));
390
-		}
391
-
392
-		$result = $select->executeQuery();
393
-		$entries = $result->fetchAll();
394
-		$result->closeCursor();
395
-
396
-		return $entries;
397
-	}
398
-
399
-	/**
400
-	 * attempts to map the given entry
401
-	 *
402
-	 * @param string $fdn fully distinguished name (from LDAP)
403
-	 * @param string $name
404
-	 * @param string $uuid a unique identifier as used in LDAP
405
-	 * @return bool
406
-	 */
407
-	public function map($fdn, $name, $uuid) {
408
-		if (mb_strlen($fdn) > 4000) {
409
-			Server::get(LoggerInterface::class)->error(
410
-				'Cannot map, because the DN exceeds 4000 characters: {dn}',
411
-				[
412
-					'app' => 'user_ldap',
413
-					'dn' => $fdn,
414
-				]
415
-			);
416
-			return false;
417
-		}
418
-
419
-		$row = [
420
-			'ldap_dn_hash' => $this->getDNHash($fdn),
421
-			'ldap_dn' => $fdn,
422
-			'owncloud_name' => $name,
423
-			'directory_uuid' => $uuid
424
-		];
425
-
426
-		try {
427
-			$result = $this->dbc->insertIfNotExist($this->getTableName(), $row);
428
-			if ((bool)$result === true) {
429
-				$this->cache[$fdn] = $name;
430
-				$this->localNameToDnCache?->set($name, $fdn, self::LOCAL_CACHE_TTL);
431
-			}
432
-			// insertIfNotExist returns values as int
433
-			return (bool)$result;
434
-		} catch (\Exception $e) {
435
-			return false;
436
-		}
437
-	}
438
-
439
-	/**
440
-	 * removes a mapping based on the owncloud_name of the entry
441
-	 *
442
-	 * @param string $name
443
-	 * @return bool
444
-	 */
445
-	public function unmap($name) {
446
-		$statement = $this->dbc->prepare('
342
+        try {
343
+            $res = $statement->execute([$prefixMatch . $this->dbc->escapeLikeParameter($search) . $postfixMatch]);
344
+        } catch (Exception $e) {
345
+            return [];
346
+        }
347
+        $names = [];
348
+        while ($row = $res->fetch()) {
349
+            $names[] = $row['owncloud_name'];
350
+        }
351
+        return $names;
352
+    }
353
+
354
+    /**
355
+     * Gets the name based on the provided LDAP UUID.
356
+     *
357
+     * @param string $uuid
358
+     * @return string|false
359
+     */
360
+    public function getNameByUUID($uuid) {
361
+        return $this->getXbyY('owncloud_name', 'directory_uuid', $uuid);
362
+    }
363
+
364
+    public function getDnByUUID($uuid) {
365
+        return $this->getXbyY('ldap_dn', 'directory_uuid', $uuid);
366
+    }
367
+
368
+    /**
369
+     * Gets the UUID based on the provided LDAP DN
370
+     *
371
+     * @param string $dn
372
+     * @return false|string
373
+     * @throws \Exception
374
+     */
375
+    public function getUUIDByDN($dn) {
376
+        return $this->getXbyY('directory_uuid', 'ldap_dn_hash', $this->getDNHash($dn));
377
+    }
378
+
379
+    public function getList(int $offset = 0, ?int $limit = null, bool $invalidatedOnly = false): array {
380
+        $select = $this->dbc->getQueryBuilder();
381
+        $select->selectAlias('ldap_dn', 'dn')
382
+            ->selectAlias('owncloud_name', 'name')
383
+            ->selectAlias('directory_uuid', 'uuid')
384
+            ->from($this->getTableName())
385
+            ->setMaxResults($limit)
386
+            ->setFirstResult($offset);
387
+
388
+        if ($invalidatedOnly) {
389
+            $select->where($select->expr()->like('directory_uuid', $select->createNamedParameter('invalidated_%')));
390
+        }
391
+
392
+        $result = $select->executeQuery();
393
+        $entries = $result->fetchAll();
394
+        $result->closeCursor();
395
+
396
+        return $entries;
397
+    }
398
+
399
+    /**
400
+     * attempts to map the given entry
401
+     *
402
+     * @param string $fdn fully distinguished name (from LDAP)
403
+     * @param string $name
404
+     * @param string $uuid a unique identifier as used in LDAP
405
+     * @return bool
406
+     */
407
+    public function map($fdn, $name, $uuid) {
408
+        if (mb_strlen($fdn) > 4000) {
409
+            Server::get(LoggerInterface::class)->error(
410
+                'Cannot map, because the DN exceeds 4000 characters: {dn}',
411
+                [
412
+                    'app' => 'user_ldap',
413
+                    'dn' => $fdn,
414
+                ]
415
+            );
416
+            return false;
417
+        }
418
+
419
+        $row = [
420
+            'ldap_dn_hash' => $this->getDNHash($fdn),
421
+            'ldap_dn' => $fdn,
422
+            'owncloud_name' => $name,
423
+            'directory_uuid' => $uuid
424
+        ];
425
+
426
+        try {
427
+            $result = $this->dbc->insertIfNotExist($this->getTableName(), $row);
428
+            if ((bool)$result === true) {
429
+                $this->cache[$fdn] = $name;
430
+                $this->localNameToDnCache?->set($name, $fdn, self::LOCAL_CACHE_TTL);
431
+            }
432
+            // insertIfNotExist returns values as int
433
+            return (bool)$result;
434
+        } catch (\Exception $e) {
435
+            return false;
436
+        }
437
+    }
438
+
439
+    /**
440
+     * removes a mapping based on the owncloud_name of the entry
441
+     *
442
+     * @param string $name
443
+     * @return bool
444
+     */
445
+    public function unmap($name) {
446
+        $statement = $this->dbc->prepare('
447 447
 			DELETE FROM `' . $this->getTableName() . '`
448 448
 			WHERE `owncloud_name` = ?');
449 449
 
450
-		$dn = array_search($name, $this->cache);
451
-		if ($dn !== false) {
452
-			unset($this->cache[$dn]);
453
-		}
454
-		$this->localNameToDnCache?->remove($name);
455
-
456
-		return $this->modify($statement, [$name]);
457
-	}
458
-
459
-	/**
460
-	 * Truncates the mapping table
461
-	 *
462
-	 * @return bool
463
-	 */
464
-	public function clear() {
465
-		$sql = $this->dbc
466
-			->getDatabasePlatform()
467
-			->getTruncateTableSQL('`' . $this->getTableName() . '`');
468
-		try {
469
-			$this->dbc->executeQuery($sql);
470
-			$this->localNameToDnCache?->clear();
471
-
472
-			return true;
473
-		} catch (Exception $e) {
474
-			return false;
475
-		}
476
-	}
477
-
478
-	/**
479
-	 * clears the mapping table one by one and executing a callback with
480
-	 * each row's id (=owncloud_name col)
481
-	 *
482
-	 * @param callable $preCallback
483
-	 * @param callable $postCallback
484
-	 * @return bool true on success, false when at least one row was not
485
-	 *              deleted
486
-	 */
487
-	public function clearCb(callable $preCallback, callable $postCallback): bool {
488
-		$picker = $this->dbc->getQueryBuilder();
489
-		$picker->select('owncloud_name')
490
-			->from($this->getTableName());
491
-		$cursor = $picker->executeQuery();
492
-		$result = true;
493
-		while (($id = $cursor->fetchOne()) !== false) {
494
-			$preCallback($id);
495
-			if ($isUnmapped = $this->unmap($id)) {
496
-				$postCallback($id);
497
-			}
498
-			$result = $result && $isUnmapped;
499
-		}
500
-		$cursor->closeCursor();
501
-		return $result;
502
-	}
503
-
504
-	/**
505
-	 * returns the number of entries in the mappings table
506
-	 *
507
-	 * @return int
508
-	 */
509
-	public function count(): int {
510
-		$query = $this->dbc->getQueryBuilder();
511
-		$query->select($query->func()->count('ldap_dn_hash'))
512
-			->from($this->getTableName());
513
-		$res = $query->executeQuery();
514
-		$count = $res->fetchOne();
515
-		$res->closeCursor();
516
-		return (int)$count;
517
-	}
518
-
519
-	public function countInvalidated(): int {
520
-		$query = $this->dbc->getQueryBuilder();
521
-		$query->select($query->func()->count('ldap_dn_hash'))
522
-			->from($this->getTableName())
523
-			->where($query->expr()->like('directory_uuid', $query->createNamedParameter('invalidated_%')));
524
-		$res = $query->executeQuery();
525
-		$count = $res->fetchOne();
526
-		$res->closeCursor();
527
-		return (int)$count;
528
-	}
450
+        $dn = array_search($name, $this->cache);
451
+        if ($dn !== false) {
452
+            unset($this->cache[$dn]);
453
+        }
454
+        $this->localNameToDnCache?->remove($name);
455
+
456
+        return $this->modify($statement, [$name]);
457
+    }
458
+
459
+    /**
460
+     * Truncates the mapping table
461
+     *
462
+     * @return bool
463
+     */
464
+    public function clear() {
465
+        $sql = $this->dbc
466
+            ->getDatabasePlatform()
467
+            ->getTruncateTableSQL('`' . $this->getTableName() . '`');
468
+        try {
469
+            $this->dbc->executeQuery($sql);
470
+            $this->localNameToDnCache?->clear();
471
+
472
+            return true;
473
+        } catch (Exception $e) {
474
+            return false;
475
+        }
476
+    }
477
+
478
+    /**
479
+     * clears the mapping table one by one and executing a callback with
480
+     * each row's id (=owncloud_name col)
481
+     *
482
+     * @param callable $preCallback
483
+     * @param callable $postCallback
484
+     * @return bool true on success, false when at least one row was not
485
+     *              deleted
486
+     */
487
+    public function clearCb(callable $preCallback, callable $postCallback): bool {
488
+        $picker = $this->dbc->getQueryBuilder();
489
+        $picker->select('owncloud_name')
490
+            ->from($this->getTableName());
491
+        $cursor = $picker->executeQuery();
492
+        $result = true;
493
+        while (($id = $cursor->fetchOne()) !== false) {
494
+            $preCallback($id);
495
+            if ($isUnmapped = $this->unmap($id)) {
496
+                $postCallback($id);
497
+            }
498
+            $result = $result && $isUnmapped;
499
+        }
500
+        $cursor->closeCursor();
501
+        return $result;
502
+    }
503
+
504
+    /**
505
+     * returns the number of entries in the mappings table
506
+     *
507
+     * @return int
508
+     */
509
+    public function count(): int {
510
+        $query = $this->dbc->getQueryBuilder();
511
+        $query->select($query->func()->count('ldap_dn_hash'))
512
+            ->from($this->getTableName());
513
+        $res = $query->executeQuery();
514
+        $count = $res->fetchOne();
515
+        $res->closeCursor();
516
+        return (int)$count;
517
+    }
518
+
519
+    public function countInvalidated(): int {
520
+        $query = $this->dbc->getQueryBuilder();
521
+        $query->select($query->func()->count('ldap_dn_hash'))
522
+            ->from($this->getTableName())
523
+            ->where($query->expr()->like('directory_uuid', $query->createNamedParameter('invalidated_%')));
524
+        $res = $query->executeQuery();
525
+        $count = $res->fetchOne();
526
+        $res->closeCursor();
527
+        return (int)$count;
528
+    }
529 529
 }
Please login to merge, or discard this patch.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -78,7 +78,7 @@  discard block
 block discarded – undo
78 78
 		}
79 79
 
80 80
 		$section = \str_contains($this->getTableName(), 'user') ? 'u/' : 'g/';
81
-		$this->localNameToDnCache = $this->cacheFactory->createLocal('ldap/map/' . $section);
81
+		$this->localNameToDnCache = $this->cacheFactory->createLocal('ldap/map/'.$section);
82 82
 
83 83
 		// We use the cache as well to store whether it shall be used. If the
84 84
 		// answer was no, we unset it again.
@@ -101,7 +101,7 @@  discard block
 block discarded – undo
101 101
 		$row = $result->fetch();
102 102
 		$result->closeCursor();
103 103
 
104
-		$use = (int)$row['count'] <= self::LOCAL_CACHE_OBJECT_THRESHOLD;
104
+		$use = (int) $row['count'] <= self::LOCAL_CACHE_OBJECT_THRESHOLD;
105 105
 		$this->localNameToDnCache->set('use', $use, self::LOCAL_USER_COUNT_TTL);
106 106
 		return $use;
107 107
 	}
@@ -175,7 +175,7 @@  discard block
 block discarded – undo
175 175
 	/**
176 176
 	 * Gets the LDAP DN based on the provided name.
177 177
 	 */
178
-	public function getDNByName(string $name): string|false {
178
+	public function getDNByName(string $name): string | false {
179 179
 		$dn = array_search($name, $this->cache, true);
180 180
 		if ($dn === false) {
181 181
 			$dn = $this->localNameToDnCache?->get($name);
@@ -200,7 +200,7 @@  discard block
 block discarded – undo
200 200
 	public function setDNbyUUID($fdn, $uuid) {
201 201
 		$oldDn = $this->getDnByUUID($uuid);
202 202
 		$statement = $this->dbc->prepare('
203
-			UPDATE `' . $this->getTableName() . '`
203
+			UPDATE `' . $this->getTableName().'`
204 204
 			SET `ldap_dn_hash` = ?, `ldap_dn` = ?
205 205
 			WHERE `directory_uuid` = ?
206 206
 		');
@@ -232,7 +232,7 @@  discard block
 block discarded – undo
232 232
 	 */
233 233
 	public function setUUIDbyDN($uuid, $fdn): bool {
234 234
 		$statement = $this->dbc->prepare('
235
-			UPDATE `' . $this->getTableName() . '`
235
+			UPDATE `' . $this->getTableName().'`
236 236
 			SET `directory_uuid` = ?
237 237
 			WHERE `ldap_dn_hash` = ?
238 238
 		');
@@ -335,12 +335,12 @@  discard block
 block discarded – undo
335 335
 	public function getNamesBySearch(string $search, string $prefixMatch = '', string $postfixMatch = ''): array {
336 336
 		$statement = $this->dbc->prepare('
337 337
 			SELECT `owncloud_name`
338
-			FROM `' . $this->getTableName() . '`
338
+			FROM `' . $this->getTableName().'`
339 339
 			WHERE `owncloud_name` LIKE ?
340 340
 		');
341 341
 
342 342
 		try {
343
-			$res = $statement->execute([$prefixMatch . $this->dbc->escapeLikeParameter($search) . $postfixMatch]);
343
+			$res = $statement->execute([$prefixMatch.$this->dbc->escapeLikeParameter($search).$postfixMatch]);
344 344
 		} catch (Exception $e) {
345 345
 			return [];
346 346
 		}
@@ -425,12 +425,12 @@  discard block
 block discarded – undo
425 425
 
426 426
 		try {
427 427
 			$result = $this->dbc->insertIfNotExist($this->getTableName(), $row);
428
-			if ((bool)$result === true) {
428
+			if ((bool) $result === true) {
429 429
 				$this->cache[$fdn] = $name;
430 430
 				$this->localNameToDnCache?->set($name, $fdn, self::LOCAL_CACHE_TTL);
431 431
 			}
432 432
 			// insertIfNotExist returns values as int
433
-			return (bool)$result;
433
+			return (bool) $result;
434 434
 		} catch (\Exception $e) {
435 435
 			return false;
436 436
 		}
@@ -444,7 +444,7 @@  discard block
 block discarded – undo
444 444
 	 */
445 445
 	public function unmap($name) {
446 446
 		$statement = $this->dbc->prepare('
447
-			DELETE FROM `' . $this->getTableName() . '`
447
+			DELETE FROM `' . $this->getTableName().'`
448 448
 			WHERE `owncloud_name` = ?');
449 449
 
450 450
 		$dn = array_search($name, $this->cache);
@@ -464,7 +464,7 @@  discard block
 block discarded – undo
464 464
 	public function clear() {
465 465
 		$sql = $this->dbc
466 466
 			->getDatabasePlatform()
467
-			->getTruncateTableSQL('`' . $this->getTableName() . '`');
467
+			->getTruncateTableSQL('`'.$this->getTableName().'`');
468 468
 		try {
469 469
 			$this->dbc->executeQuery($sql);
470 470
 			$this->localNameToDnCache?->clear();
@@ -513,7 +513,7 @@  discard block
 block discarded – undo
513 513
 		$res = $query->executeQuery();
514 514
 		$count = $res->fetchOne();
515 515
 		$res->closeCursor();
516
-		return (int)$count;
516
+		return (int) $count;
517 517
 	}
518 518
 
519 519
 	public function countInvalidated(): int {
@@ -524,6 +524,6 @@  discard block
 block discarded – undo
524 524
 		$res = $query->executeQuery();
525 525
 		$count = $res->fetchOne();
526 526
 		$res->closeCursor();
527
-		return (int)$count;
527
+		return (int) $count;
528 528
 	}
529 529
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/User_LDAP.php 2 patches
Indentation   +620 added lines, -620 removed lines patch added patch discarded remove patch
@@ -23,624 +23,624 @@
 block discarded – undo
23 23
 use Psr\Log\LoggerInterface;
24 24
 
25 25
 class User_LDAP extends BackendUtility implements IUserBackend, UserInterface, IUserLDAP, ILimitAwareCountUsersBackend, ICountMappedUsersBackend, IProvideEnabledStateBackend {
26
-	public function __construct(
27
-		Access $access,
28
-		protected INotificationManager $notificationManager,
29
-		protected UserPluginManager $userPluginManager,
30
-		protected LoggerInterface $logger,
31
-		protected DeletedUsersIndex $deletedUsersIndex,
32
-	) {
33
-		parent::__construct($access);
34
-	}
35
-
36
-	/**
37
-	 * checks whether the user is allowed to change their avatar in Nextcloud
38
-	 *
39
-	 * @param string $uid the Nextcloud user name
40
-	 * @return boolean either the user can or cannot
41
-	 * @throws \Exception
42
-	 */
43
-	public function canChangeAvatar($uid) {
44
-		if ($this->userPluginManager->implementsActions(Backend::PROVIDE_AVATAR)) {
45
-			return $this->userPluginManager->canChangeAvatar($uid);
46
-		}
47
-
48
-		if (!$this->implementsActions(Backend::PROVIDE_AVATAR)) {
49
-			return true;
50
-		}
51
-
52
-		$user = $this->access->userManager->get($uid);
53
-		if (!$user instanceof User) {
54
-			return false;
55
-		}
56
-		$imageData = $user->getAvatarImage();
57
-		if ($imageData === false) {
58
-			return true;
59
-		}
60
-		return !$user->updateAvatar(true);
61
-	}
62
-
63
-	/**
64
-	 * Return the username for the given login name, if available
65
-	 *
66
-	 * @param string $loginName
67
-	 * @return string|false
68
-	 * @throws \Exception
69
-	 */
70
-	public function loginName2UserName($loginName, bool $forceLdapRefetch = false) {
71
-		$cacheKey = 'loginName2UserName-' . $loginName;
72
-		$username = $this->access->connection->getFromCache($cacheKey);
73
-		$knownDn = $username ? $this->access->username2dn($username) : false;
74
-
75
-		$ignoreCache = (($username === false || $knownDn === false) && $forceLdapRefetch);
76
-		if ($username !== null && !$ignoreCache) {
77
-			return $username;
78
-		}
79
-
80
-		try {
81
-			$ldapRecord = $this->getLDAPUserByLoginName($loginName);
82
-			$user = $this->access->userManager->get($ldapRecord['dn'][0]);
83
-			if ($user === null || $user instanceof OfflineUser) {
84
-				// this path is not really possible, however get() is documented
85
-				// to return User, OfflineUser or null so we are very defensive here.
86
-				$this->access->connection->writeToCache($cacheKey, false);
87
-				return false;
88
-			}
89
-			$username = $user->getUsername();
90
-			$this->access->connection->writeToCache($cacheKey, $username);
91
-			if ($forceLdapRefetch) {
92
-				$user->processAttributes($ldapRecord);
93
-			}
94
-			return $username;
95
-		} catch (NotOnLDAP $e) {
96
-			$this->access->connection->writeToCache($cacheKey, false);
97
-			return false;
98
-		}
99
-	}
100
-
101
-	/**
102
-	 * returns the username for the given LDAP DN, if available
103
-	 *
104
-	 * @param string $dn
105
-	 * @return string|false with the username
106
-	 */
107
-	public function dn2UserName($dn) {
108
-		return $this->access->dn2username($dn);
109
-	}
110
-
111
-	/**
112
-	 * returns an LDAP record based on a given login name
113
-	 *
114
-	 * @param string $loginName
115
-	 * @return array
116
-	 * @throws NotOnLDAP
117
-	 */
118
-	public function getLDAPUserByLoginName($loginName) {
119
-		//find out dn of the user name
120
-		$attrs = $this->access->userManager->getAttributes();
121
-		$users = $this->access->fetchUsersByLoginName($loginName, $attrs);
122
-		if (count($users) < 1) {
123
-			throw new NotOnLDAP('No user available for the given login name on '
124
-				. $this->access->connection->ldapHost . ':' . $this->access->connection->ldapPort);
125
-		}
126
-		return $users[0];
127
-	}
128
-
129
-	/**
130
-	 * Check if the password is correct without logging in the user
131
-	 *
132
-	 * @param string $uid The username
133
-	 * @param string $password The password
134
-	 * @return false|string
135
-	 */
136
-	public function checkPassword($uid, $password) {
137
-		$username = $this->loginName2UserName($uid, true);
138
-		if ($username === false) {
139
-			return false;
140
-		}
141
-		$dn = $this->access->username2dn($username);
142
-		if ($dn === false) {
143
-			$this->logger->warning(
144
-				'LDAP Login: Found user {user}, but no assigned DN',
145
-				[
146
-					'app' => 'user_ldap',
147
-					'user' => $username,
148
-				]
149
-			);
150
-			return false;
151
-		}
152
-		$user = $this->access->userManager->get($dn);
153
-
154
-		if (!$user instanceof User) {
155
-			$this->logger->warning(
156
-				'LDAP Login: Could not get user object for DN ' . $dn
157
-				. '. Maybe the LDAP entry has no set display name attribute?',
158
-				['app' => 'user_ldap']
159
-			);
160
-			return false;
161
-		}
162
-		if ($user->getUsername() !== false) {
163
-			//are the credentials OK?
164
-			if (!$this->access->areCredentialsValid($dn, $password)) {
165
-				return false;
166
-			}
167
-
168
-			$this->access->cacheUserExists($user->getUsername());
169
-			$user->markLogin();
170
-
171
-			return $user->getUsername();
172
-		}
173
-
174
-		return false;
175
-	}
176
-
177
-	/**
178
-	 * Set password
179
-	 * @param string $uid The username
180
-	 * @param string $password The new password
181
-	 * @return bool
182
-	 */
183
-	public function setPassword($uid, $password) {
184
-		if ($this->userPluginManager->implementsActions(Backend::SET_PASSWORD)) {
185
-			return $this->userPluginManager->setPassword($uid, $password);
186
-		}
187
-
188
-		$user = $this->access->userManager->get($uid);
189
-
190
-		if (!$user instanceof User) {
191
-			throw new \Exception('LDAP setPassword: Could not get user object for uid ' . $uid
192
-				. '. Maybe the LDAP entry has no set display name attribute?');
193
-		}
194
-		if ($user->getUsername() !== false && $this->access->setPassword($user->getDN(), $password)) {
195
-			$ldapDefaultPPolicyDN = $this->access->connection->ldapDefaultPPolicyDN;
196
-			$turnOnPasswordChange = $this->access->connection->turnOnPasswordChange;
197
-			if (!empty($ldapDefaultPPolicyDN) && ((int)$turnOnPasswordChange === 1)) {
198
-				//remove last password expiry warning if any
199
-				$notification = $this->notificationManager->createNotification();
200
-				$notification->setApp('user_ldap')
201
-					->setUser($uid)
202
-					->setObject('pwd_exp_warn', $uid)
203
-				;
204
-				$this->notificationManager->markProcessed($notification);
205
-			}
206
-			return true;
207
-		}
208
-
209
-		return false;
210
-	}
211
-
212
-	/**
213
-	 * Get a list of all users
214
-	 *
215
-	 * @param string $search
216
-	 * @param integer $limit
217
-	 * @param integer $offset
218
-	 * @return string[] an array of all uids
219
-	 */
220
-	public function getUsers($search = '', $limit = 10, $offset = 0) {
221
-		$search = $this->access->escapeFilterPart($search, true);
222
-		$cachekey = 'getUsers-' . $search . '-' . $limit . '-' . $offset;
223
-
224
-		//check if users are cached, if so return
225
-		$ldap_users = $this->access->connection->getFromCache($cachekey);
226
-		if (!is_null($ldap_users)) {
227
-			return $ldap_users;
228
-		}
229
-
230
-		// if we'd pass -1 to LDAP search, we'd end up in a Protocol
231
-		// error. With a limit of 0, we get 0 results. So we pass null.
232
-		if ($limit <= 0) {
233
-			$limit = null;
234
-		}
235
-		$filter = $this->access->combineFilterWithAnd([
236
-			$this->access->connection->ldapUserFilter,
237
-			$this->access->connection->ldapUserDisplayName . '=*',
238
-			$this->access->getFilterPartForUserSearch($search)
239
-		]);
240
-
241
-		$this->logger->debug(
242
-			'getUsers: Options: search ' . $search . ' limit ' . $limit . ' offset ' . $offset . ' Filter: ' . $filter,
243
-			['app' => 'user_ldap']
244
-		);
245
-		//do the search and translate results to Nextcloud names
246
-		$ldap_users = $this->access->fetchListOfUsers(
247
-			$filter,
248
-			$this->access->userManager->getAttributes(true),
249
-			$limit, $offset);
250
-		$ldap_users = $this->access->nextcloudUserNames($ldap_users);
251
-		$this->logger->debug(
252
-			'getUsers: ' . count($ldap_users) . ' Users found',
253
-			['app' => 'user_ldap']
254
-		);
255
-
256
-		$this->access->connection->writeToCache($cachekey, $ldap_users);
257
-		return $ldap_users;
258
-	}
259
-
260
-	/**
261
-	 * checks whether a user is still available on LDAP
262
-	 *
263
-	 * @param string|User $user either the Nextcloud user
264
-	 *                          name or an instance of that user
265
-	 * @throws \Exception
266
-	 * @throws \OC\ServerNotAvailableException
267
-	 */
268
-	public function userExistsOnLDAP($user, bool $ignoreCache = false): bool {
269
-		if (is_string($user)) {
270
-			$user = $this->access->userManager->get($user);
271
-		}
272
-		if (is_null($user)) {
273
-			return false;
274
-		}
275
-		$uid = $user instanceof User ? $user->getUsername() : $user->getOCName();
276
-		$cacheKey = 'userExistsOnLDAP' . $uid;
277
-		if (!$ignoreCache) {
278
-			$userExists = $this->access->connection->getFromCache($cacheKey);
279
-			if (!is_null($userExists)) {
280
-				return (bool)$userExists;
281
-			}
282
-		}
283
-
284
-		$dn = $user->getDN();
285
-		//check if user really still exists by reading its entry
286
-		if (!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapUserFilter))) {
287
-			try {
288
-				$uuid = $this->access->getUserMapper()->getUUIDByDN($dn);
289
-				if (!$uuid) {
290
-					$this->access->connection->writeToCache($cacheKey, false);
291
-					return false;
292
-				}
293
-				$newDn = $this->access->getUserDnByUuid($uuid);
294
-				//check if renamed user is still valid by reapplying the ldap filter
295
-				if ($newDn === $dn || !is_array($this->access->readAttribute($newDn, '', $this->access->connection->ldapUserFilter))) {
296
-					$this->access->connection->writeToCache($cacheKey, false);
297
-					return false;
298
-				}
299
-				$this->access->getUserMapper()->setDNbyUUID($newDn, $uuid);
300
-			} catch (ServerNotAvailableException $e) {
301
-				throw $e;
302
-			} catch (\Exception $e) {
303
-				$this->access->connection->writeToCache($cacheKey, false);
304
-				return false;
305
-			}
306
-		}
307
-
308
-		if ($user instanceof OfflineUser) {
309
-			$user->unmark();
310
-		}
311
-
312
-		$this->access->connection->writeToCache($cacheKey, true);
313
-		return true;
314
-	}
315
-
316
-	/**
317
-	 * check if a user exists
318
-	 * @param string $uid the username
319
-	 * @return boolean
320
-	 * @throws \Exception when connection could not be established
321
-	 */
322
-	public function userExists($uid) {
323
-		$userExists = $this->access->connection->getFromCache('userExists' . $uid);
324
-		if (!is_null($userExists)) {
325
-			return (bool)$userExists;
326
-		}
327
-		$userExists = $this->access->userManager->exists($uid);
328
-
329
-		if (!$userExists) {
330
-			$this->logger->debug(
331
-				'No DN found for ' . $uid . ' on ' . $this->access->connection->ldapHost,
332
-				['app' => 'user_ldap']
333
-			);
334
-			$this->access->connection->writeToCache('userExists' . $uid, false);
335
-			return false;
336
-		}
337
-
338
-		$this->access->connection->writeToCache('userExists' . $uid, true);
339
-		return true;
340
-	}
341
-
342
-	/**
343
-	 * returns whether a user was deleted in LDAP
344
-	 *
345
-	 * @param string $uid The username of the user to delete
346
-	 * @return bool
347
-	 */
348
-	public function deleteUser($uid) {
349
-		if ($this->userPluginManager->canDeleteUser()) {
350
-			$status = $this->userPluginManager->deleteUser($uid);
351
-			if ($status === false) {
352
-				return false;
353
-			}
354
-		}
355
-
356
-		$marked = $this->deletedUsersIndex->isUserMarked($uid);
357
-		if (!$marked) {
358
-			try {
359
-				$user = $this->access->userManager->get($uid);
360
-				if (($user instanceof User) && !$this->userExistsOnLDAP($uid, true)) {
361
-					$user->markUser();
362
-					$marked = true;
363
-				}
364
-			} catch (\Exception $e) {
365
-				$this->logger->debug(
366
-					$e->getMessage(),
367
-					['app' => 'user_ldap', 'exception' => $e]
368
-				);
369
-			}
370
-			if (!$marked) {
371
-				$this->logger->notice(
372
-					'User ' . $uid . ' is not marked as deleted, not cleaning up.',
373
-					['app' => 'user_ldap']
374
-				);
375
-				return false;
376
-			}
377
-		}
378
-		$this->logger->info('Cleaning up after user ' . $uid,
379
-			['app' => 'user_ldap']);
380
-
381
-		$this->access->getUserMapper()->unmap($uid); // we don't emit unassign signals here, since it is implicit to delete signals fired from core
382
-		$this->access->userManager->invalidate($uid);
383
-		$this->access->connection->clearCache();
384
-		return true;
385
-	}
386
-
387
-	/**
388
-	 * get the user's home directory
389
-	 *
390
-	 * @param string $uid the username
391
-	 * @return bool|string
392
-	 * @throws NoUserException
393
-	 * @throws \Exception
394
-	 */
395
-	public function getHome($uid) {
396
-		// user Exists check required as it is not done in user proxy!
397
-		if (!$this->userExists($uid)) {
398
-			return false;
399
-		}
400
-
401
-		if ($this->userPluginManager->implementsActions(Backend::GET_HOME)) {
402
-			return $this->userPluginManager->getHome($uid);
403
-		}
404
-
405
-		$cacheKey = 'getHome' . $uid;
406
-		$path = $this->access->connection->getFromCache($cacheKey);
407
-		if (!is_null($path)) {
408
-			return $path;
409
-		}
410
-
411
-		// early return path if it is a deleted user
412
-		$user = $this->access->userManager->get($uid);
413
-		if ($user instanceof User || $user instanceof OfflineUser) {
414
-			$path = $user->getHomePath() ?: false;
415
-		} else {
416
-			throw new NoUserException($uid . ' is not a valid user anymore');
417
-		}
418
-
419
-		$this->access->cacheUserHome($uid, $path);
420
-		return $path;
421
-	}
422
-
423
-	/**
424
-	 * get display name of the user
425
-	 * @param string $uid user ID of the user
426
-	 * @return string|false display name
427
-	 */
428
-	public function getDisplayName($uid) {
429
-		if ($this->userPluginManager->implementsActions(Backend::GET_DISPLAYNAME)) {
430
-			return $this->userPluginManager->getDisplayName($uid);
431
-		}
432
-
433
-		if (!$this->userExists($uid)) {
434
-			return false;
435
-		}
436
-
437
-		$cacheKey = 'getDisplayName' . $uid;
438
-		if (!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
439
-			return $displayName;
440
-		}
441
-
442
-		//Check whether the display name is configured to have a 2nd feature
443
-		$additionalAttribute = $this->access->connection->ldapUserDisplayName2;
444
-		$displayName2 = '';
445
-		if ($additionalAttribute !== '') {
446
-			$displayName2 = $this->access->readAttribute(
447
-				$this->access->username2dn($uid),
448
-				$additionalAttribute);
449
-		}
450
-
451
-		$displayName = $this->access->readAttribute(
452
-			$this->access->username2dn($uid),
453
-			$this->access->connection->ldapUserDisplayName);
454
-
455
-		if ($displayName && (count($displayName) > 0)) {
456
-			$displayName = $displayName[0];
457
-
458
-			if (is_array($displayName2)) {
459
-				$displayName2 = count($displayName2) > 0 ? $displayName2[0] : '';
460
-			}
461
-
462
-			$user = $this->access->userManager->get($uid);
463
-			if ($user instanceof User) {
464
-				$displayName = $user->composeAndStoreDisplayName($displayName, (string)$displayName2);
465
-				$this->access->connection->writeToCache($cacheKey, $displayName);
466
-			}
467
-			if ($user instanceof OfflineUser) {
468
-				$displayName = $user->getDisplayName();
469
-			}
470
-			return $displayName;
471
-		}
472
-
473
-		return null;
474
-	}
475
-
476
-	/**
477
-	 * set display name of the user
478
-	 * @param string $uid user ID of the user
479
-	 * @param string $displayName new display name of the user
480
-	 * @return string|false display name
481
-	 */
482
-	public function setDisplayName($uid, $displayName) {
483
-		if ($this->userPluginManager->implementsActions(Backend::SET_DISPLAYNAME)) {
484
-			$this->userPluginManager->setDisplayName($uid, $displayName);
485
-			$this->access->cacheUserDisplayName($uid, $displayName);
486
-			return $displayName;
487
-		}
488
-		return false;
489
-	}
490
-
491
-	/**
492
-	 * Get a list of all display names
493
-	 *
494
-	 * @param string $search
495
-	 * @param int|null $limit
496
-	 * @param int|null $offset
497
-	 * @return array an array of all displayNames (value) and the corresponding uids (key)
498
-	 */
499
-	public function getDisplayNames($search = '', $limit = null, $offset = null) {
500
-		$cacheKey = 'getDisplayNames-' . $search . '-' . $limit . '-' . $offset;
501
-		if (!is_null($displayNames = $this->access->connection->getFromCache($cacheKey))) {
502
-			return $displayNames;
503
-		}
504
-
505
-		$displayNames = [];
506
-		$users = $this->getUsers($search, $limit, $offset);
507
-		foreach ($users as $user) {
508
-			$displayNames[$user] = $this->getDisplayName($user);
509
-		}
510
-		$this->access->connection->writeToCache($cacheKey, $displayNames);
511
-		return $displayNames;
512
-	}
513
-
514
-	/**
515
-	 * Check if backend implements actions
516
-	 * @param int $actions bitwise-or'ed actions
517
-	 * @return boolean
518
-	 *
519
-	 * Returns the supported actions as int to be
520
-	 * compared with \OC\User\Backend::CREATE_USER etc.
521
-	 */
522
-	public function implementsActions($actions) {
523
-		return (bool)((Backend::CHECK_PASSWORD
524
-			| Backend::GET_HOME
525
-			| Backend::GET_DISPLAYNAME
526
-			| (($this->access->connection->ldapUserAvatarRule !== 'none') ? Backend::PROVIDE_AVATAR : 0)
527
-			| Backend::COUNT_USERS
528
-			| (((int)$this->access->connection->turnOnPasswordChange === 1)? Backend::SET_PASSWORD :0)
529
-			| $this->userPluginManager->getImplementedActions())
530
-			& $actions);
531
-	}
532
-
533
-	/**
534
-	 * @return bool
535
-	 */
536
-	public function hasUserListings() {
537
-		return true;
538
-	}
539
-
540
-	/**
541
-	 * counts the users in LDAP
542
-	 */
543
-	public function countUsers(int $limit = 0): int|false {
544
-		if ($this->userPluginManager->implementsActions(Backend::COUNT_USERS)) {
545
-			return $this->userPluginManager->countUsers();
546
-		}
547
-
548
-		$filter = $this->access->getFilterForUserCount();
549
-		$cacheKey = 'countUsers-' . $filter . '-' . $limit;
550
-		if (!is_null($entries = $this->access->connection->getFromCache($cacheKey))) {
551
-			return $entries;
552
-		}
553
-		$entries = $this->access->countUsers($filter, limit:$limit);
554
-		$this->access->connection->writeToCache($cacheKey, $entries);
555
-		return $entries;
556
-	}
557
-
558
-	public function countMappedUsers(): int {
559
-		return $this->access->getUserMapper()->count();
560
-	}
561
-
562
-	/**
563
-	 * Backend name to be shown in user management
564
-	 * @return string the name of the backend to be shown
565
-	 */
566
-	public function getBackendName() {
567
-		return 'LDAP';
568
-	}
569
-
570
-	/**
571
-	 * Return access for LDAP interaction.
572
-	 * @param string $uid
573
-	 * @return Access instance of Access for LDAP interaction
574
-	 */
575
-	public function getLDAPAccess($uid) {
576
-		return $this->access;
577
-	}
578
-
579
-	/**
580
-	 * Return LDAP connection resource from a cloned connection.
581
-	 * The cloned connection needs to be closed manually.
582
-	 * of the current access.
583
-	 * @param string $uid
584
-	 * @return \LDAP\Connection The LDAP connection
585
-	 */
586
-	public function getNewLDAPConnection($uid) {
587
-		$connection = clone $this->access->getConnection();
588
-		return $connection->getConnectionResource();
589
-	}
590
-
591
-	/**
592
-	 * create new user
593
-	 * @param string $username username of the new user
594
-	 * @param string $password password of the new user
595
-	 * @throws \UnexpectedValueException
596
-	 * @return bool
597
-	 */
598
-	public function createUser($username, $password) {
599
-		if ($this->userPluginManager->implementsActions(Backend::CREATE_USER)) {
600
-			if ($dn = $this->userPluginManager->createUser($username, $password)) {
601
-				if (is_string($dn)) {
602
-					// the NC user creation work flow requires a know user id up front
603
-					$uuid = $this->access->getUUID($dn, true);
604
-					if (is_string($uuid)) {
605
-						$this->access->mapAndAnnounceIfApplicable(
606
-							$this->access->getUserMapper(),
607
-							$dn,
608
-							$username,
609
-							$uuid,
610
-							true
611
-						);
612
-					} else {
613
-						$this->logger->warning(
614
-							'Failed to map created LDAP user with userid {userid}, because UUID could not be determined',
615
-							[
616
-								'app' => 'user_ldap',
617
-								'userid' => $username,
618
-							]
619
-						);
620
-					}
621
-				} else {
622
-					throw new \UnexpectedValueException('LDAP Plugin: Method createUser changed to return the user DN instead of boolean.');
623
-				}
624
-			}
625
-			return (bool)$dn;
626
-		}
627
-		return false;
628
-	}
629
-
630
-	public function isUserEnabled(string $uid, callable $queryDatabaseValue): bool {
631
-		if ($this->deletedUsersIndex->isUserMarked($uid) && ((int)$this->access->connection->markRemnantsAsDisabled === 1)) {
632
-			return false;
633
-		} else {
634
-			return $queryDatabaseValue();
635
-		}
636
-	}
637
-
638
-	public function setUserEnabled(string $uid, bool $enabled, callable $queryDatabaseValue, callable $setDatabaseValue): bool {
639
-		$setDatabaseValue($enabled);
640
-		return $enabled;
641
-	}
642
-
643
-	public function getDisabledUserList(?int $limit = null, int $offset = 0, string $search = ''): array {
644
-		throw new \Exception('This is implemented directly in User_Proxy');
645
-	}
26
+    public function __construct(
27
+        Access $access,
28
+        protected INotificationManager $notificationManager,
29
+        protected UserPluginManager $userPluginManager,
30
+        protected LoggerInterface $logger,
31
+        protected DeletedUsersIndex $deletedUsersIndex,
32
+    ) {
33
+        parent::__construct($access);
34
+    }
35
+
36
+    /**
37
+     * checks whether the user is allowed to change their avatar in Nextcloud
38
+     *
39
+     * @param string $uid the Nextcloud user name
40
+     * @return boolean either the user can or cannot
41
+     * @throws \Exception
42
+     */
43
+    public function canChangeAvatar($uid) {
44
+        if ($this->userPluginManager->implementsActions(Backend::PROVIDE_AVATAR)) {
45
+            return $this->userPluginManager->canChangeAvatar($uid);
46
+        }
47
+
48
+        if (!$this->implementsActions(Backend::PROVIDE_AVATAR)) {
49
+            return true;
50
+        }
51
+
52
+        $user = $this->access->userManager->get($uid);
53
+        if (!$user instanceof User) {
54
+            return false;
55
+        }
56
+        $imageData = $user->getAvatarImage();
57
+        if ($imageData === false) {
58
+            return true;
59
+        }
60
+        return !$user->updateAvatar(true);
61
+    }
62
+
63
+    /**
64
+     * Return the username for the given login name, if available
65
+     *
66
+     * @param string $loginName
67
+     * @return string|false
68
+     * @throws \Exception
69
+     */
70
+    public function loginName2UserName($loginName, bool $forceLdapRefetch = false) {
71
+        $cacheKey = 'loginName2UserName-' . $loginName;
72
+        $username = $this->access->connection->getFromCache($cacheKey);
73
+        $knownDn = $username ? $this->access->username2dn($username) : false;
74
+
75
+        $ignoreCache = (($username === false || $knownDn === false) && $forceLdapRefetch);
76
+        if ($username !== null && !$ignoreCache) {
77
+            return $username;
78
+        }
79
+
80
+        try {
81
+            $ldapRecord = $this->getLDAPUserByLoginName($loginName);
82
+            $user = $this->access->userManager->get($ldapRecord['dn'][0]);
83
+            if ($user === null || $user instanceof OfflineUser) {
84
+                // this path is not really possible, however get() is documented
85
+                // to return User, OfflineUser or null so we are very defensive here.
86
+                $this->access->connection->writeToCache($cacheKey, false);
87
+                return false;
88
+            }
89
+            $username = $user->getUsername();
90
+            $this->access->connection->writeToCache($cacheKey, $username);
91
+            if ($forceLdapRefetch) {
92
+                $user->processAttributes($ldapRecord);
93
+            }
94
+            return $username;
95
+        } catch (NotOnLDAP $e) {
96
+            $this->access->connection->writeToCache($cacheKey, false);
97
+            return false;
98
+        }
99
+    }
100
+
101
+    /**
102
+     * returns the username for the given LDAP DN, if available
103
+     *
104
+     * @param string $dn
105
+     * @return string|false with the username
106
+     */
107
+    public function dn2UserName($dn) {
108
+        return $this->access->dn2username($dn);
109
+    }
110
+
111
+    /**
112
+     * returns an LDAP record based on a given login name
113
+     *
114
+     * @param string $loginName
115
+     * @return array
116
+     * @throws NotOnLDAP
117
+     */
118
+    public function getLDAPUserByLoginName($loginName) {
119
+        //find out dn of the user name
120
+        $attrs = $this->access->userManager->getAttributes();
121
+        $users = $this->access->fetchUsersByLoginName($loginName, $attrs);
122
+        if (count($users) < 1) {
123
+            throw new NotOnLDAP('No user available for the given login name on '
124
+                . $this->access->connection->ldapHost . ':' . $this->access->connection->ldapPort);
125
+        }
126
+        return $users[0];
127
+    }
128
+
129
+    /**
130
+     * Check if the password is correct without logging in the user
131
+     *
132
+     * @param string $uid The username
133
+     * @param string $password The password
134
+     * @return false|string
135
+     */
136
+    public function checkPassword($uid, $password) {
137
+        $username = $this->loginName2UserName($uid, true);
138
+        if ($username === false) {
139
+            return false;
140
+        }
141
+        $dn = $this->access->username2dn($username);
142
+        if ($dn === false) {
143
+            $this->logger->warning(
144
+                'LDAP Login: Found user {user}, but no assigned DN',
145
+                [
146
+                    'app' => 'user_ldap',
147
+                    'user' => $username,
148
+                ]
149
+            );
150
+            return false;
151
+        }
152
+        $user = $this->access->userManager->get($dn);
153
+
154
+        if (!$user instanceof User) {
155
+            $this->logger->warning(
156
+                'LDAP Login: Could not get user object for DN ' . $dn
157
+                . '. Maybe the LDAP entry has no set display name attribute?',
158
+                ['app' => 'user_ldap']
159
+            );
160
+            return false;
161
+        }
162
+        if ($user->getUsername() !== false) {
163
+            //are the credentials OK?
164
+            if (!$this->access->areCredentialsValid($dn, $password)) {
165
+                return false;
166
+            }
167
+
168
+            $this->access->cacheUserExists($user->getUsername());
169
+            $user->markLogin();
170
+
171
+            return $user->getUsername();
172
+        }
173
+
174
+        return false;
175
+    }
176
+
177
+    /**
178
+     * Set password
179
+     * @param string $uid The username
180
+     * @param string $password The new password
181
+     * @return bool
182
+     */
183
+    public function setPassword($uid, $password) {
184
+        if ($this->userPluginManager->implementsActions(Backend::SET_PASSWORD)) {
185
+            return $this->userPluginManager->setPassword($uid, $password);
186
+        }
187
+
188
+        $user = $this->access->userManager->get($uid);
189
+
190
+        if (!$user instanceof User) {
191
+            throw new \Exception('LDAP setPassword: Could not get user object for uid ' . $uid
192
+                . '. Maybe the LDAP entry has no set display name attribute?');
193
+        }
194
+        if ($user->getUsername() !== false && $this->access->setPassword($user->getDN(), $password)) {
195
+            $ldapDefaultPPolicyDN = $this->access->connection->ldapDefaultPPolicyDN;
196
+            $turnOnPasswordChange = $this->access->connection->turnOnPasswordChange;
197
+            if (!empty($ldapDefaultPPolicyDN) && ((int)$turnOnPasswordChange === 1)) {
198
+                //remove last password expiry warning if any
199
+                $notification = $this->notificationManager->createNotification();
200
+                $notification->setApp('user_ldap')
201
+                    ->setUser($uid)
202
+                    ->setObject('pwd_exp_warn', $uid)
203
+                ;
204
+                $this->notificationManager->markProcessed($notification);
205
+            }
206
+            return true;
207
+        }
208
+
209
+        return false;
210
+    }
211
+
212
+    /**
213
+     * Get a list of all users
214
+     *
215
+     * @param string $search
216
+     * @param integer $limit
217
+     * @param integer $offset
218
+     * @return string[] an array of all uids
219
+     */
220
+    public function getUsers($search = '', $limit = 10, $offset = 0) {
221
+        $search = $this->access->escapeFilterPart($search, true);
222
+        $cachekey = 'getUsers-' . $search . '-' . $limit . '-' . $offset;
223
+
224
+        //check if users are cached, if so return
225
+        $ldap_users = $this->access->connection->getFromCache($cachekey);
226
+        if (!is_null($ldap_users)) {
227
+            return $ldap_users;
228
+        }
229
+
230
+        // if we'd pass -1 to LDAP search, we'd end up in a Protocol
231
+        // error. With a limit of 0, we get 0 results. So we pass null.
232
+        if ($limit <= 0) {
233
+            $limit = null;
234
+        }
235
+        $filter = $this->access->combineFilterWithAnd([
236
+            $this->access->connection->ldapUserFilter,
237
+            $this->access->connection->ldapUserDisplayName . '=*',
238
+            $this->access->getFilterPartForUserSearch($search)
239
+        ]);
240
+
241
+        $this->logger->debug(
242
+            'getUsers: Options: search ' . $search . ' limit ' . $limit . ' offset ' . $offset . ' Filter: ' . $filter,
243
+            ['app' => 'user_ldap']
244
+        );
245
+        //do the search and translate results to Nextcloud names
246
+        $ldap_users = $this->access->fetchListOfUsers(
247
+            $filter,
248
+            $this->access->userManager->getAttributes(true),
249
+            $limit, $offset);
250
+        $ldap_users = $this->access->nextcloudUserNames($ldap_users);
251
+        $this->logger->debug(
252
+            'getUsers: ' . count($ldap_users) . ' Users found',
253
+            ['app' => 'user_ldap']
254
+        );
255
+
256
+        $this->access->connection->writeToCache($cachekey, $ldap_users);
257
+        return $ldap_users;
258
+    }
259
+
260
+    /**
261
+     * checks whether a user is still available on LDAP
262
+     *
263
+     * @param string|User $user either the Nextcloud user
264
+     *                          name or an instance of that user
265
+     * @throws \Exception
266
+     * @throws \OC\ServerNotAvailableException
267
+     */
268
+    public function userExistsOnLDAP($user, bool $ignoreCache = false): bool {
269
+        if (is_string($user)) {
270
+            $user = $this->access->userManager->get($user);
271
+        }
272
+        if (is_null($user)) {
273
+            return false;
274
+        }
275
+        $uid = $user instanceof User ? $user->getUsername() : $user->getOCName();
276
+        $cacheKey = 'userExistsOnLDAP' . $uid;
277
+        if (!$ignoreCache) {
278
+            $userExists = $this->access->connection->getFromCache($cacheKey);
279
+            if (!is_null($userExists)) {
280
+                return (bool)$userExists;
281
+            }
282
+        }
283
+
284
+        $dn = $user->getDN();
285
+        //check if user really still exists by reading its entry
286
+        if (!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapUserFilter))) {
287
+            try {
288
+                $uuid = $this->access->getUserMapper()->getUUIDByDN($dn);
289
+                if (!$uuid) {
290
+                    $this->access->connection->writeToCache($cacheKey, false);
291
+                    return false;
292
+                }
293
+                $newDn = $this->access->getUserDnByUuid($uuid);
294
+                //check if renamed user is still valid by reapplying the ldap filter
295
+                if ($newDn === $dn || !is_array($this->access->readAttribute($newDn, '', $this->access->connection->ldapUserFilter))) {
296
+                    $this->access->connection->writeToCache($cacheKey, false);
297
+                    return false;
298
+                }
299
+                $this->access->getUserMapper()->setDNbyUUID($newDn, $uuid);
300
+            } catch (ServerNotAvailableException $e) {
301
+                throw $e;
302
+            } catch (\Exception $e) {
303
+                $this->access->connection->writeToCache($cacheKey, false);
304
+                return false;
305
+            }
306
+        }
307
+
308
+        if ($user instanceof OfflineUser) {
309
+            $user->unmark();
310
+        }
311
+
312
+        $this->access->connection->writeToCache($cacheKey, true);
313
+        return true;
314
+    }
315
+
316
+    /**
317
+     * check if a user exists
318
+     * @param string $uid the username
319
+     * @return boolean
320
+     * @throws \Exception when connection could not be established
321
+     */
322
+    public function userExists($uid) {
323
+        $userExists = $this->access->connection->getFromCache('userExists' . $uid);
324
+        if (!is_null($userExists)) {
325
+            return (bool)$userExists;
326
+        }
327
+        $userExists = $this->access->userManager->exists($uid);
328
+
329
+        if (!$userExists) {
330
+            $this->logger->debug(
331
+                'No DN found for ' . $uid . ' on ' . $this->access->connection->ldapHost,
332
+                ['app' => 'user_ldap']
333
+            );
334
+            $this->access->connection->writeToCache('userExists' . $uid, false);
335
+            return false;
336
+        }
337
+
338
+        $this->access->connection->writeToCache('userExists' . $uid, true);
339
+        return true;
340
+    }
341
+
342
+    /**
343
+     * returns whether a user was deleted in LDAP
344
+     *
345
+     * @param string $uid The username of the user to delete
346
+     * @return bool
347
+     */
348
+    public function deleteUser($uid) {
349
+        if ($this->userPluginManager->canDeleteUser()) {
350
+            $status = $this->userPluginManager->deleteUser($uid);
351
+            if ($status === false) {
352
+                return false;
353
+            }
354
+        }
355
+
356
+        $marked = $this->deletedUsersIndex->isUserMarked($uid);
357
+        if (!$marked) {
358
+            try {
359
+                $user = $this->access->userManager->get($uid);
360
+                if (($user instanceof User) && !$this->userExistsOnLDAP($uid, true)) {
361
+                    $user->markUser();
362
+                    $marked = true;
363
+                }
364
+            } catch (\Exception $e) {
365
+                $this->logger->debug(
366
+                    $e->getMessage(),
367
+                    ['app' => 'user_ldap', 'exception' => $e]
368
+                );
369
+            }
370
+            if (!$marked) {
371
+                $this->logger->notice(
372
+                    'User ' . $uid . ' is not marked as deleted, not cleaning up.',
373
+                    ['app' => 'user_ldap']
374
+                );
375
+                return false;
376
+            }
377
+        }
378
+        $this->logger->info('Cleaning up after user ' . $uid,
379
+            ['app' => 'user_ldap']);
380
+
381
+        $this->access->getUserMapper()->unmap($uid); // we don't emit unassign signals here, since it is implicit to delete signals fired from core
382
+        $this->access->userManager->invalidate($uid);
383
+        $this->access->connection->clearCache();
384
+        return true;
385
+    }
386
+
387
+    /**
388
+     * get the user's home directory
389
+     *
390
+     * @param string $uid the username
391
+     * @return bool|string
392
+     * @throws NoUserException
393
+     * @throws \Exception
394
+     */
395
+    public function getHome($uid) {
396
+        // user Exists check required as it is not done in user proxy!
397
+        if (!$this->userExists($uid)) {
398
+            return false;
399
+        }
400
+
401
+        if ($this->userPluginManager->implementsActions(Backend::GET_HOME)) {
402
+            return $this->userPluginManager->getHome($uid);
403
+        }
404
+
405
+        $cacheKey = 'getHome' . $uid;
406
+        $path = $this->access->connection->getFromCache($cacheKey);
407
+        if (!is_null($path)) {
408
+            return $path;
409
+        }
410
+
411
+        // early return path if it is a deleted user
412
+        $user = $this->access->userManager->get($uid);
413
+        if ($user instanceof User || $user instanceof OfflineUser) {
414
+            $path = $user->getHomePath() ?: false;
415
+        } else {
416
+            throw new NoUserException($uid . ' is not a valid user anymore');
417
+        }
418
+
419
+        $this->access->cacheUserHome($uid, $path);
420
+        return $path;
421
+    }
422
+
423
+    /**
424
+     * get display name of the user
425
+     * @param string $uid user ID of the user
426
+     * @return string|false display name
427
+     */
428
+    public function getDisplayName($uid) {
429
+        if ($this->userPluginManager->implementsActions(Backend::GET_DISPLAYNAME)) {
430
+            return $this->userPluginManager->getDisplayName($uid);
431
+        }
432
+
433
+        if (!$this->userExists($uid)) {
434
+            return false;
435
+        }
436
+
437
+        $cacheKey = 'getDisplayName' . $uid;
438
+        if (!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
439
+            return $displayName;
440
+        }
441
+
442
+        //Check whether the display name is configured to have a 2nd feature
443
+        $additionalAttribute = $this->access->connection->ldapUserDisplayName2;
444
+        $displayName2 = '';
445
+        if ($additionalAttribute !== '') {
446
+            $displayName2 = $this->access->readAttribute(
447
+                $this->access->username2dn($uid),
448
+                $additionalAttribute);
449
+        }
450
+
451
+        $displayName = $this->access->readAttribute(
452
+            $this->access->username2dn($uid),
453
+            $this->access->connection->ldapUserDisplayName);
454
+
455
+        if ($displayName && (count($displayName) > 0)) {
456
+            $displayName = $displayName[0];
457
+
458
+            if (is_array($displayName2)) {
459
+                $displayName2 = count($displayName2) > 0 ? $displayName2[0] : '';
460
+            }
461
+
462
+            $user = $this->access->userManager->get($uid);
463
+            if ($user instanceof User) {
464
+                $displayName = $user->composeAndStoreDisplayName($displayName, (string)$displayName2);
465
+                $this->access->connection->writeToCache($cacheKey, $displayName);
466
+            }
467
+            if ($user instanceof OfflineUser) {
468
+                $displayName = $user->getDisplayName();
469
+            }
470
+            return $displayName;
471
+        }
472
+
473
+        return null;
474
+    }
475
+
476
+    /**
477
+     * set display name of the user
478
+     * @param string $uid user ID of the user
479
+     * @param string $displayName new display name of the user
480
+     * @return string|false display name
481
+     */
482
+    public function setDisplayName($uid, $displayName) {
483
+        if ($this->userPluginManager->implementsActions(Backend::SET_DISPLAYNAME)) {
484
+            $this->userPluginManager->setDisplayName($uid, $displayName);
485
+            $this->access->cacheUserDisplayName($uid, $displayName);
486
+            return $displayName;
487
+        }
488
+        return false;
489
+    }
490
+
491
+    /**
492
+     * Get a list of all display names
493
+     *
494
+     * @param string $search
495
+     * @param int|null $limit
496
+     * @param int|null $offset
497
+     * @return array an array of all displayNames (value) and the corresponding uids (key)
498
+     */
499
+    public function getDisplayNames($search = '', $limit = null, $offset = null) {
500
+        $cacheKey = 'getDisplayNames-' . $search . '-' . $limit . '-' . $offset;
501
+        if (!is_null($displayNames = $this->access->connection->getFromCache($cacheKey))) {
502
+            return $displayNames;
503
+        }
504
+
505
+        $displayNames = [];
506
+        $users = $this->getUsers($search, $limit, $offset);
507
+        foreach ($users as $user) {
508
+            $displayNames[$user] = $this->getDisplayName($user);
509
+        }
510
+        $this->access->connection->writeToCache($cacheKey, $displayNames);
511
+        return $displayNames;
512
+    }
513
+
514
+    /**
515
+     * Check if backend implements actions
516
+     * @param int $actions bitwise-or'ed actions
517
+     * @return boolean
518
+     *
519
+     * Returns the supported actions as int to be
520
+     * compared with \OC\User\Backend::CREATE_USER etc.
521
+     */
522
+    public function implementsActions($actions) {
523
+        return (bool)((Backend::CHECK_PASSWORD
524
+            | Backend::GET_HOME
525
+            | Backend::GET_DISPLAYNAME
526
+            | (($this->access->connection->ldapUserAvatarRule !== 'none') ? Backend::PROVIDE_AVATAR : 0)
527
+            | Backend::COUNT_USERS
528
+            | (((int)$this->access->connection->turnOnPasswordChange === 1)? Backend::SET_PASSWORD :0)
529
+            | $this->userPluginManager->getImplementedActions())
530
+            & $actions);
531
+    }
532
+
533
+    /**
534
+     * @return bool
535
+     */
536
+    public function hasUserListings() {
537
+        return true;
538
+    }
539
+
540
+    /**
541
+     * counts the users in LDAP
542
+     */
543
+    public function countUsers(int $limit = 0): int|false {
544
+        if ($this->userPluginManager->implementsActions(Backend::COUNT_USERS)) {
545
+            return $this->userPluginManager->countUsers();
546
+        }
547
+
548
+        $filter = $this->access->getFilterForUserCount();
549
+        $cacheKey = 'countUsers-' . $filter . '-' . $limit;
550
+        if (!is_null($entries = $this->access->connection->getFromCache($cacheKey))) {
551
+            return $entries;
552
+        }
553
+        $entries = $this->access->countUsers($filter, limit:$limit);
554
+        $this->access->connection->writeToCache($cacheKey, $entries);
555
+        return $entries;
556
+    }
557
+
558
+    public function countMappedUsers(): int {
559
+        return $this->access->getUserMapper()->count();
560
+    }
561
+
562
+    /**
563
+     * Backend name to be shown in user management
564
+     * @return string the name of the backend to be shown
565
+     */
566
+    public function getBackendName() {
567
+        return 'LDAP';
568
+    }
569
+
570
+    /**
571
+     * Return access for LDAP interaction.
572
+     * @param string $uid
573
+     * @return Access instance of Access for LDAP interaction
574
+     */
575
+    public function getLDAPAccess($uid) {
576
+        return $this->access;
577
+    }
578
+
579
+    /**
580
+     * Return LDAP connection resource from a cloned connection.
581
+     * The cloned connection needs to be closed manually.
582
+     * of the current access.
583
+     * @param string $uid
584
+     * @return \LDAP\Connection The LDAP connection
585
+     */
586
+    public function getNewLDAPConnection($uid) {
587
+        $connection = clone $this->access->getConnection();
588
+        return $connection->getConnectionResource();
589
+    }
590
+
591
+    /**
592
+     * create new user
593
+     * @param string $username username of the new user
594
+     * @param string $password password of the new user
595
+     * @throws \UnexpectedValueException
596
+     * @return bool
597
+     */
598
+    public function createUser($username, $password) {
599
+        if ($this->userPluginManager->implementsActions(Backend::CREATE_USER)) {
600
+            if ($dn = $this->userPluginManager->createUser($username, $password)) {
601
+                if (is_string($dn)) {
602
+                    // the NC user creation work flow requires a know user id up front
603
+                    $uuid = $this->access->getUUID($dn, true);
604
+                    if (is_string($uuid)) {
605
+                        $this->access->mapAndAnnounceIfApplicable(
606
+                            $this->access->getUserMapper(),
607
+                            $dn,
608
+                            $username,
609
+                            $uuid,
610
+                            true
611
+                        );
612
+                    } else {
613
+                        $this->logger->warning(
614
+                            'Failed to map created LDAP user with userid {userid}, because UUID could not be determined',
615
+                            [
616
+                                'app' => 'user_ldap',
617
+                                'userid' => $username,
618
+                            ]
619
+                        );
620
+                    }
621
+                } else {
622
+                    throw new \UnexpectedValueException('LDAP Plugin: Method createUser changed to return the user DN instead of boolean.');
623
+                }
624
+            }
625
+            return (bool)$dn;
626
+        }
627
+        return false;
628
+    }
629
+
630
+    public function isUserEnabled(string $uid, callable $queryDatabaseValue): bool {
631
+        if ($this->deletedUsersIndex->isUserMarked($uid) && ((int)$this->access->connection->markRemnantsAsDisabled === 1)) {
632
+            return false;
633
+        } else {
634
+            return $queryDatabaseValue();
635
+        }
636
+    }
637
+
638
+    public function setUserEnabled(string $uid, bool $enabled, callable $queryDatabaseValue, callable $setDatabaseValue): bool {
639
+        $setDatabaseValue($enabled);
640
+        return $enabled;
641
+    }
642
+
643
+    public function getDisabledUserList(?int $limit = null, int $offset = 0, string $search = ''): array {
644
+        throw new \Exception('This is implemented directly in User_Proxy');
645
+    }
646 646
 }
Please login to merge, or discard this patch.
Spacing   +29 added lines, -29 removed lines patch added patch discarded remove patch
@@ -68,7 +68,7 @@  discard block
 block discarded – undo
68 68
 	 * @throws \Exception
69 69
 	 */
70 70
 	public function loginName2UserName($loginName, bool $forceLdapRefetch = false) {
71
-		$cacheKey = 'loginName2UserName-' . $loginName;
71
+		$cacheKey = 'loginName2UserName-'.$loginName;
72 72
 		$username = $this->access->connection->getFromCache($cacheKey);
73 73
 		$knownDn = $username ? $this->access->username2dn($username) : false;
74 74
 
@@ -121,7 +121,7 @@  discard block
 block discarded – undo
121 121
 		$users = $this->access->fetchUsersByLoginName($loginName, $attrs);
122 122
 		if (count($users) < 1) {
123 123
 			throw new NotOnLDAP('No user available for the given login name on '
124
-				. $this->access->connection->ldapHost . ':' . $this->access->connection->ldapPort);
124
+				. $this->access->connection->ldapHost.':'.$this->access->connection->ldapPort);
125 125
 		}
126 126
 		return $users[0];
127 127
 	}
@@ -153,7 +153,7 @@  discard block
 block discarded – undo
153 153
 
154 154
 		if (!$user instanceof User) {
155 155
 			$this->logger->warning(
156
-				'LDAP Login: Could not get user object for DN ' . $dn
156
+				'LDAP Login: Could not get user object for DN '.$dn
157 157
 				. '. Maybe the LDAP entry has no set display name attribute?',
158 158
 				['app' => 'user_ldap']
159 159
 			);
@@ -188,13 +188,13 @@  discard block
 block discarded – undo
188 188
 		$user = $this->access->userManager->get($uid);
189 189
 
190 190
 		if (!$user instanceof User) {
191
-			throw new \Exception('LDAP setPassword: Could not get user object for uid ' . $uid
191
+			throw new \Exception('LDAP setPassword: Could not get user object for uid '.$uid
192 192
 				. '. Maybe the LDAP entry has no set display name attribute?');
193 193
 		}
194 194
 		if ($user->getUsername() !== false && $this->access->setPassword($user->getDN(), $password)) {
195 195
 			$ldapDefaultPPolicyDN = $this->access->connection->ldapDefaultPPolicyDN;
196 196
 			$turnOnPasswordChange = $this->access->connection->turnOnPasswordChange;
197
-			if (!empty($ldapDefaultPPolicyDN) && ((int)$turnOnPasswordChange === 1)) {
197
+			if (!empty($ldapDefaultPPolicyDN) && ((int) $turnOnPasswordChange === 1)) {
198 198
 				//remove last password expiry warning if any
199 199
 				$notification = $this->notificationManager->createNotification();
200 200
 				$notification->setApp('user_ldap')
@@ -219,7 +219,7 @@  discard block
 block discarded – undo
219 219
 	 */
220 220
 	public function getUsers($search = '', $limit = 10, $offset = 0) {
221 221
 		$search = $this->access->escapeFilterPart($search, true);
222
-		$cachekey = 'getUsers-' . $search . '-' . $limit . '-' . $offset;
222
+		$cachekey = 'getUsers-'.$search.'-'.$limit.'-'.$offset;
223 223
 
224 224
 		//check if users are cached, if so return
225 225
 		$ldap_users = $this->access->connection->getFromCache($cachekey);
@@ -234,12 +234,12 @@  discard block
 block discarded – undo
234 234
 		}
235 235
 		$filter = $this->access->combineFilterWithAnd([
236 236
 			$this->access->connection->ldapUserFilter,
237
-			$this->access->connection->ldapUserDisplayName . '=*',
237
+			$this->access->connection->ldapUserDisplayName.'=*',
238 238
 			$this->access->getFilterPartForUserSearch($search)
239 239
 		]);
240 240
 
241 241
 		$this->logger->debug(
242
-			'getUsers: Options: search ' . $search . ' limit ' . $limit . ' offset ' . $offset . ' Filter: ' . $filter,
242
+			'getUsers: Options: search '.$search.' limit '.$limit.' offset '.$offset.' Filter: '.$filter,
243 243
 			['app' => 'user_ldap']
244 244
 		);
245 245
 		//do the search and translate results to Nextcloud names
@@ -249,7 +249,7 @@  discard block
 block discarded – undo
249 249
 			$limit, $offset);
250 250
 		$ldap_users = $this->access->nextcloudUserNames($ldap_users);
251 251
 		$this->logger->debug(
252
-			'getUsers: ' . count($ldap_users) . ' Users found',
252
+			'getUsers: '.count($ldap_users).' Users found',
253 253
 			['app' => 'user_ldap']
254 254
 		);
255 255
 
@@ -273,11 +273,11 @@  discard block
 block discarded – undo
273 273
 			return false;
274 274
 		}
275 275
 		$uid = $user instanceof User ? $user->getUsername() : $user->getOCName();
276
-		$cacheKey = 'userExistsOnLDAP' . $uid;
276
+		$cacheKey = 'userExistsOnLDAP'.$uid;
277 277
 		if (!$ignoreCache) {
278 278
 			$userExists = $this->access->connection->getFromCache($cacheKey);
279 279
 			if (!is_null($userExists)) {
280
-				return (bool)$userExists;
280
+				return (bool) $userExists;
281 281
 			}
282 282
 		}
283 283
 
@@ -320,22 +320,22 @@  discard block
 block discarded – undo
320 320
 	 * @throws \Exception when connection could not be established
321 321
 	 */
322 322
 	public function userExists($uid) {
323
-		$userExists = $this->access->connection->getFromCache('userExists' . $uid);
323
+		$userExists = $this->access->connection->getFromCache('userExists'.$uid);
324 324
 		if (!is_null($userExists)) {
325
-			return (bool)$userExists;
325
+			return (bool) $userExists;
326 326
 		}
327 327
 		$userExists = $this->access->userManager->exists($uid);
328 328
 
329 329
 		if (!$userExists) {
330 330
 			$this->logger->debug(
331
-				'No DN found for ' . $uid . ' on ' . $this->access->connection->ldapHost,
331
+				'No DN found for '.$uid.' on '.$this->access->connection->ldapHost,
332 332
 				['app' => 'user_ldap']
333 333
 			);
334
-			$this->access->connection->writeToCache('userExists' . $uid, false);
334
+			$this->access->connection->writeToCache('userExists'.$uid, false);
335 335
 			return false;
336 336
 		}
337 337
 
338
-		$this->access->connection->writeToCache('userExists' . $uid, true);
338
+		$this->access->connection->writeToCache('userExists'.$uid, true);
339 339
 		return true;
340 340
 	}
341 341
 
@@ -369,13 +369,13 @@  discard block
 block discarded – undo
369 369
 			}
370 370
 			if (!$marked) {
371 371
 				$this->logger->notice(
372
-					'User ' . $uid . ' is not marked as deleted, not cleaning up.',
372
+					'User '.$uid.' is not marked as deleted, not cleaning up.',
373 373
 					['app' => 'user_ldap']
374 374
 				);
375 375
 				return false;
376 376
 			}
377 377
 		}
378
-		$this->logger->info('Cleaning up after user ' . $uid,
378
+		$this->logger->info('Cleaning up after user '.$uid,
379 379
 			['app' => 'user_ldap']);
380 380
 
381 381
 		$this->access->getUserMapper()->unmap($uid); // we don't emit unassign signals here, since it is implicit to delete signals fired from core
@@ -402,7 +402,7 @@  discard block
 block discarded – undo
402 402
 			return $this->userPluginManager->getHome($uid);
403 403
 		}
404 404
 
405
-		$cacheKey = 'getHome' . $uid;
405
+		$cacheKey = 'getHome'.$uid;
406 406
 		$path = $this->access->connection->getFromCache($cacheKey);
407 407
 		if (!is_null($path)) {
408 408
 			return $path;
@@ -413,7 +413,7 @@  discard block
 block discarded – undo
413 413
 		if ($user instanceof User || $user instanceof OfflineUser) {
414 414
 			$path = $user->getHomePath() ?: false;
415 415
 		} else {
416
-			throw new NoUserException($uid . ' is not a valid user anymore');
416
+			throw new NoUserException($uid.' is not a valid user anymore');
417 417
 		}
418 418
 
419 419
 		$this->access->cacheUserHome($uid, $path);
@@ -434,7 +434,7 @@  discard block
 block discarded – undo
434 434
 			return false;
435 435
 		}
436 436
 
437
-		$cacheKey = 'getDisplayName' . $uid;
437
+		$cacheKey = 'getDisplayName'.$uid;
438 438
 		if (!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
439 439
 			return $displayName;
440 440
 		}
@@ -461,7 +461,7 @@  discard block
 block discarded – undo
461 461
 
462 462
 			$user = $this->access->userManager->get($uid);
463 463
 			if ($user instanceof User) {
464
-				$displayName = $user->composeAndStoreDisplayName($displayName, (string)$displayName2);
464
+				$displayName = $user->composeAndStoreDisplayName($displayName, (string) $displayName2);
465 465
 				$this->access->connection->writeToCache($cacheKey, $displayName);
466 466
 			}
467 467
 			if ($user instanceof OfflineUser) {
@@ -497,7 +497,7 @@  discard block
 block discarded – undo
497 497
 	 * @return array an array of all displayNames (value) and the corresponding uids (key)
498 498
 	 */
499 499
 	public function getDisplayNames($search = '', $limit = null, $offset = null) {
500
-		$cacheKey = 'getDisplayNames-' . $search . '-' . $limit . '-' . $offset;
500
+		$cacheKey = 'getDisplayNames-'.$search.'-'.$limit.'-'.$offset;
501 501
 		if (!is_null($displayNames = $this->access->connection->getFromCache($cacheKey))) {
502 502
 			return $displayNames;
503 503
 		}
@@ -520,12 +520,12 @@  discard block
 block discarded – undo
520 520
 	 * compared with \OC\User\Backend::CREATE_USER etc.
521 521
 	 */
522 522
 	public function implementsActions($actions) {
523
-		return (bool)((Backend::CHECK_PASSWORD
523
+		return (bool) ((Backend::CHECK_PASSWORD
524 524
 			| Backend::GET_HOME
525 525
 			| Backend::GET_DISPLAYNAME
526 526
 			| (($this->access->connection->ldapUserAvatarRule !== 'none') ? Backend::PROVIDE_AVATAR : 0)
527 527
 			| Backend::COUNT_USERS
528
-			| (((int)$this->access->connection->turnOnPasswordChange === 1)? Backend::SET_PASSWORD :0)
528
+			| (((int) $this->access->connection->turnOnPasswordChange === 1) ? Backend::SET_PASSWORD : 0)
529 529
 			| $this->userPluginManager->getImplementedActions())
530 530
 			& $actions);
531 531
 	}
@@ -540,13 +540,13 @@  discard block
 block discarded – undo
540 540
 	/**
541 541
 	 * counts the users in LDAP
542 542
 	 */
543
-	public function countUsers(int $limit = 0): int|false {
543
+	public function countUsers(int $limit = 0): int | false {
544 544
 		if ($this->userPluginManager->implementsActions(Backend::COUNT_USERS)) {
545 545
 			return $this->userPluginManager->countUsers();
546 546
 		}
547 547
 
548 548
 		$filter = $this->access->getFilterForUserCount();
549
-		$cacheKey = 'countUsers-' . $filter . '-' . $limit;
549
+		$cacheKey = 'countUsers-'.$filter.'-'.$limit;
550 550
 		if (!is_null($entries = $this->access->connection->getFromCache($cacheKey))) {
551 551
 			return $entries;
552 552
 		}
@@ -622,13 +622,13 @@  discard block
 block discarded – undo
622 622
 					throw new \UnexpectedValueException('LDAP Plugin: Method createUser changed to return the user DN instead of boolean.');
623 623
 				}
624 624
 			}
625
-			return (bool)$dn;
625
+			return (bool) $dn;
626 626
 		}
627 627
 		return false;
628 628
 	}
629 629
 
630 630
 	public function isUserEnabled(string $uid, callable $queryDatabaseValue): bool {
631
-		if ($this->deletedUsersIndex->isUserMarked($uid) && ((int)$this->access->connection->markRemnantsAsDisabled === 1)) {
631
+		if ($this->deletedUsersIndex->isUserMarked($uid) && ((int) $this->access->connection->markRemnantsAsDisabled === 1)) {
632 632
 			return false;
633 633
 		} else {
634 634
 			return $queryDatabaseValue();
Please login to merge, or discard this patch.
apps/user_ldap/ajax/clearMappings.php 1 patch
Indentation   +26 added lines, -26 removed lines patch added patch discarded remove patch
@@ -22,32 +22,32 @@
 block discarded – undo
22 22
 $subject = (string)$_POST['ldap_clear_mapping'];
23 23
 $mapping = null;
24 24
 try {
25
-	if ($subject === 'user') {
26
-		$mapping = Server::get(UserMapping::class);
27
-		/** @var IEventDispatcher $dispatcher */
28
-		$dispatcher = Server::get(IEventDispatcher::class);
29
-		$result = $mapping->clearCb(
30
-			function (string $uid) use ($dispatcher): void {
31
-				$dispatcher->dispatchTyped(new BeforeUserIdUnassignedEvent($uid));
32
-				/** @psalm-suppress UndefinedInterfaceMethod For now we have to emit, will be removed when all hooks are removed */
33
-				Server::get(IUserManager::class)->emit('\OC\User', 'preUnassignedUserId', [$uid]);
34
-			},
35
-			function (string $uid) use ($dispatcher): void {
36
-				$dispatcher->dispatchTyped(new UserIdUnassignedEvent($uid));
37
-				/** @psalm-suppress UndefinedInterfaceMethod For now we have to emit, will be removed when all hooks are removed */
38
-				Server::get(IUserManager::class)->emit('\OC\User', 'postUnassignedUserId', [$uid]);
39
-			}
40
-		);
41
-	} elseif ($subject === 'group') {
42
-		$mapping = Server::get(GroupMapping::class);
43
-		$result = $mapping->clear();
44
-	}
25
+    if ($subject === 'user') {
26
+        $mapping = Server::get(UserMapping::class);
27
+        /** @var IEventDispatcher $dispatcher */
28
+        $dispatcher = Server::get(IEventDispatcher::class);
29
+        $result = $mapping->clearCb(
30
+            function (string $uid) use ($dispatcher): void {
31
+                $dispatcher->dispatchTyped(new BeforeUserIdUnassignedEvent($uid));
32
+                /** @psalm-suppress UndefinedInterfaceMethod For now we have to emit, will be removed when all hooks are removed */
33
+                Server::get(IUserManager::class)->emit('\OC\User', 'preUnassignedUserId', [$uid]);
34
+            },
35
+            function (string $uid) use ($dispatcher): void {
36
+                $dispatcher->dispatchTyped(new UserIdUnassignedEvent($uid));
37
+                /** @psalm-suppress UndefinedInterfaceMethod For now we have to emit, will be removed when all hooks are removed */
38
+                Server::get(IUserManager::class)->emit('\OC\User', 'postUnassignedUserId', [$uid]);
39
+            }
40
+        );
41
+    } elseif ($subject === 'group') {
42
+        $mapping = Server::get(GroupMapping::class);
43
+        $result = $mapping->clear();
44
+    }
45 45
 
46
-	if ($mapping === null || !$result) {
47
-		$l = Util::getL10N('user_ldap');
48
-		throw new \Exception($l->t('Failed to clear the mappings.'));
49
-	}
50
-	\OC_JSON::success();
46
+    if ($mapping === null || !$result) {
47
+        $l = Util::getL10N('user_ldap');
48
+        throw new \Exception($l->t('Failed to clear the mappings.'));
49
+    }
50
+    \OC_JSON::success();
51 51
 } catch (\Exception $e) {
52
-	\OC_JSON::error(['message' => $e->getMessage()]);
52
+    \OC_JSON::error(['message' => $e->getMessage()]);
53 53
 }
Please login to merge, or discard this patch.
apps/user_ldap/tests/Mapping/GroupMappingTest.php 1 patch
Indentation   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -21,7 +21,7 @@
 block discarded – undo
21 21
  * @package OCA\User_LDAP\Tests\Mapping
22 22
  */
23 23
 class GroupMappingTest extends AbstractMappingTestCase {
24
-	public function getMapper(IDBConnection $dbMock, ICacheFactory $cacheFactory, IAppConfig $appConfig): GroupMapping {
25
-		return new GroupMapping($dbMock, $cacheFactory, $appConfig, true);
26
-	}
24
+    public function getMapper(IDBConnection $dbMock, ICacheFactory $cacheFactory, IAppConfig $appConfig): GroupMapping {
25
+        return new GroupMapping($dbMock, $cacheFactory, $appConfig, true);
26
+    }
27 27
 }
Please login to merge, or discard this patch.
apps/user_ldap/tests/Mapping/UserMappingTest.php 1 patch
Indentation   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -22,7 +22,7 @@
 block discarded – undo
22 22
  * @package OCA\User_LDAP\Tests\Mapping
23 23
  */
24 24
 class UserMappingTest extends AbstractMappingTestCase {
25
-	public function getMapper(IDBConnection $dbMock, ICacheFactory $cacheFactory, IAppConfig $appConfig): UserMapping {
26
-		return new UserMapping($dbMock, $cacheFactory, $appConfig, true, $this->createMock(IAssertion::class));
27
-	}
25
+    public function getMapper(IDBConnection $dbMock, ICacheFactory $cacheFactory, IAppConfig $appConfig): UserMapping {
26
+        return new UserMapping($dbMock, $cacheFactory, $appConfig, true, $this->createMock(IAssertion::class));
27
+    }
28 28
 }
Please login to merge, or discard this patch.
apps/user_ldap/tests/Mapping/AbstractMappingTestCase.php 1 patch
Indentation   +279 added lines, -279 removed lines patch added patch discarded remove patch
@@ -17,283 +17,283 @@
 block discarded – undo
17 17
 
18 18
 abstract class AbstractMappingTestCase extends \Test\TestCase {
19 19
 
20
-	private ICacheFactory&MockObject $cacheFactoryMock;
21
-
22
-	private IAppConfig&MockObject $configMock;
23
-
24
-	abstract public function getMapper(IDBConnection $dbMock, ICacheFactory $cacheFactory, IAppConfig $appConfig): AbstractMapping;
25
-
26
-	protected function setUp(): void {
27
-		$this->cacheFactoryMock = $this->createMock(ICacheFactory::class);
28
-		$this->configMock = $this->createMock(IAppConfig::class);
29
-	}
30
-
31
-	/**
32
-	 * kiss test on isColNameValid
33
-	 */
34
-	public function testIsColNameValid(): void {
35
-		$dbMock = $this->createMock(IDBConnection::class);
36
-		$mapper = $this->getMapper($dbMock, $this->cacheFactoryMock, $this->configMock);
37
-
38
-		$this->assertTrue($mapper->isColNameValid('ldap_dn'));
39
-		$this->assertFalse($mapper->isColNameValid('foobar'));
40
-	}
41
-
42
-	/**
43
-	 * returns an array of test entries with dn, name and uuid as keys
44
-	 * @return array
45
-	 */
46
-	protected static function getTestData(): array {
47
-		return [
48
-			[
49
-				'dn' => 'uid=foobar,dc=example,dc=org',
50
-				'name' => 'Foobar',
51
-				'uuid' => '1111-AAAA-1234-CDEF',
52
-			],
53
-			[
54
-				'dn' => 'uid=barfoo,dc=example,dc=org',
55
-				'name' => 'Barfoo',
56
-				'uuid' => '2222-BBBB-1234-CDEF',
57
-			],
58
-			[
59
-				'dn' => 'uid=barabara,dc=example,dc=org',
60
-				'name' => 'BaraBara',
61
-				'uuid' => '3333-CCCC-1234-CDEF',
62
-			]
63
-		];
64
-	}
65
-
66
-	/**
67
-	 * calls map() on the given mapper and asserts result for true
68
-	 * @param AbstractMapping $mapper
69
-	 * @param array $data
70
-	 */
71
-	protected function mapEntries(AbstractMapping $mapper, array $data): void {
72
-		foreach ($data as $entry) {
73
-			$done = $mapper->map($entry['dn'], $entry['name'], $entry['uuid']);
74
-			$this->assertTrue($done);
75
-		}
76
-	}
77
-
78
-	/**
79
-	 * initializes environment for a test run and returns an array with
80
-	 * test objects. Preparing environment means that all mappings are cleared
81
-	 * first and then filled with test entries.
82
-	 * @return array 0 = \OCA\User_LDAP\Mapping\AbstractMapping, 1 = array of
83
-	 *               users or groups
84
-	 */
85
-	private function initTest(): array {
86
-		$dbc = Server::get(IDBConnection::class);
87
-		$mapper = $this->getMapper($dbc, $this->cacheFactoryMock, $this->configMock);
88
-		$data = $this->getTestData();
89
-		// make sure DB is pristine, then fill it with test entries
90
-		$mapper->clear();
91
-		$this->mapEntries($mapper, $data);
92
-
93
-		return [$mapper, $data];
94
-	}
95
-
96
-	/**
97
-	 * tests map() method with input that should result in not-mapping.
98
-	 * Hint: successful mapping is tested inherently with mapEntries().
99
-	 */
100
-	public function testMap(): void {
101
-		[$mapper, $data] = $this->initTest();
102
-
103
-		// test that mapping will not happen when it shall not
104
-		$tooLongDN = 'uid=joann,ou=Secret Small Specialized Department,ou=Some Tremendously Important Department,ou=Another Very Important Department,ou=Pretty Meaningful Derpartment,ou=Quite Broad And General Department,ou=The Topmost Department,dc=hugelysuccessfulcompany,dc=com';
105
-		$paramKeys = ['', 'dn', 'name', 'uuid', $tooLongDN];
106
-		foreach ($paramKeys as $key) {
107
-			$failEntry = $data[0];
108
-			if (!empty($key)) {
109
-				$failEntry[$key] = 'do-not-get-mapped';
110
-			}
111
-			$isMapped = $mapper->map($failEntry['dn'], $failEntry['name'], $failEntry['uuid']);
112
-			$this->assertFalse($isMapped);
113
-		}
114
-	}
115
-
116
-	/**
117
-	 * tests unmap() for both successful and unsuccessful removing of
118
-	 * mapping entries
119
-	 */
120
-	public function testUnmap(): void {
121
-		[$mapper, $data] = $this->initTest();
122
-
123
-		foreach ($data as $entry) {
124
-			$fdnBefore = $mapper->getDNByName($entry['name']);
125
-			$result = $mapper->unmap($entry['name']);
126
-			$fdnAfter = $mapper->getDNByName($entry['name']);
127
-			$this->assertTrue($result);
128
-			$this->assertSame($fdnBefore, $entry['dn']);
129
-			$this->assertFalse($fdnAfter);
130
-		}
131
-
132
-		$result = $mapper->unmap('notAnEntry');
133
-		$this->assertFalse($result);
134
-	}
135
-
136
-	/**
137
-	 * tests getDNByName(), getNameByDN() and getNameByUUID() for successful
138
-	 * and unsuccessful requests.
139
-	 */
140
-	public function testGetMethods(): void {
141
-		[$mapper, $data] = $this->initTest();
142
-
143
-		foreach ($data as $entry) {
144
-			$fdn = $mapper->getDNByName($entry['name']);
145
-			$this->assertSame($fdn, $entry['dn']);
146
-		}
147
-		$fdn = $mapper->getDNByName('nosuchname');
148
-		$this->assertFalse($fdn);
149
-
150
-		foreach ($data as $entry) {
151
-			$name = $mapper->getNameByDN($entry['dn']);
152
-			$this->assertSame($name, $entry['name']);
153
-		}
154
-		$name = $mapper->getNameByDN('nosuchdn');
155
-		$this->assertFalse($name);
156
-
157
-		foreach ($data as $entry) {
158
-			$name = $mapper->getNameByUUID($entry['uuid']);
159
-			$this->assertSame($name, $entry['name']);
160
-		}
161
-		$name = $mapper->getNameByUUID('nosuchuuid');
162
-		$this->assertFalse($name);
163
-	}
164
-
165
-	/**
166
-	 * tests getNamesBySearch() for successful and unsuccessful requests.
167
-	 */
168
-	public function testSearch(): void {
169
-		[$mapper,] = $this->initTest();
170
-
171
-		$names = $mapper->getNamesBySearch('oo', '%', '%');
172
-		$this->assertIsArray($names);
173
-		$this->assertSame(2, count($names));
174
-		$this->assertContains('Foobar', $names);
175
-		$this->assertContains('Barfoo', $names);
176
-		$names = $mapper->getNamesBySearch('nada');
177
-		$this->assertIsArray($names);
178
-		$this->assertCount(0, $names);
179
-	}
180
-
181
-	/**
182
-	 * tests setDNbyUUID() for successful and unsuccessful update.
183
-	 */
184
-	public function testSetDNMethod(): void {
185
-		[$mapper, $data] = $this->initTest();
186
-
187
-		$newDN = 'uid=modified,dc=example,dc=org';
188
-		$done = $mapper->setDNbyUUID($newDN, $data[0]['uuid']);
189
-		$this->assertTrue($done);
190
-		$fdn = $mapper->getDNByName($data[0]['name']);
191
-		$this->assertSame($fdn, $newDN);
192
-
193
-		$newDN = 'uid=notme,dc=example,dc=org';
194
-		$done = $mapper->setDNbyUUID($newDN, 'iamnothere');
195
-		$this->assertFalse($done);
196
-		$name = $mapper->getNameByDN($newDN);
197
-		$this->assertFalse($name);
198
-	}
199
-
200
-	/**
201
-	 * tests setUUIDbyDN() for successful and unsuccessful update.
202
-	 */
203
-	public function testSetUUIDMethod(): void {
204
-		/** @var AbstractMapping $mapper */
205
-		[$mapper, $data] = $this->initTest();
206
-
207
-		$newUUID = 'ABC737-DEF754';
208
-
209
-		$done = $mapper->setUUIDbyDN($newUUID, 'uid=notme,dc=example,dc=org');
210
-		$this->assertFalse($done);
211
-		$name = $mapper->getNameByUUID($newUUID);
212
-		$this->assertFalse($name);
213
-
214
-		$done = $mapper->setUUIDbyDN($newUUID, $data[0]['dn']);
215
-		$this->assertTrue($done);
216
-		$uuid = $mapper->getUUIDByDN($data[0]['dn']);
217
-		$this->assertSame($uuid, $newUUID);
218
-	}
219
-
220
-	/**
221
-	 * tests clear() for successful update.
222
-	 */
223
-	public function testClear(): void {
224
-		[$mapper, $data] = $this->initTest();
225
-
226
-		$done = $mapper->clear();
227
-		$this->assertTrue($done);
228
-		foreach ($data as $entry) {
229
-			$name = $mapper->getNameByUUID($entry['uuid']);
230
-			$this->assertFalse($name);
231
-		}
232
-	}
233
-
234
-	/**
235
-	 * tests clear() for successful update.
236
-	 */
237
-	public function testClearCb(): void {
238
-		[$mapper, $data] = $this->initTest();
239
-
240
-		$callbackCalls = 0;
241
-		$test = $this;
242
-
243
-		$callback = function (string $id) use ($test, &$callbackCalls): void {
244
-			$test->assertTrue(trim($id) !== '');
245
-			$callbackCalls++;
246
-		};
247
-
248
-		$done = $mapper->clearCb($callback, $callback);
249
-		$this->assertTrue($done);
250
-		$this->assertSame(count($data) * 2, $callbackCalls);
251
-		foreach ($data as $entry) {
252
-			$name = $mapper->getNameByUUID($entry['uuid']);
253
-			$this->assertFalse($name);
254
-		}
255
-	}
256
-
257
-	/**
258
-	 * tests getList() method
259
-	 */
260
-	public function testList(): void {
261
-		[$mapper, $data] = $this->initTest();
262
-
263
-		// get all entries without specifying offset or limit
264
-		$results = $mapper->getList();
265
-		$this->assertCount(3, $results);
266
-
267
-		// get all-1 entries by specifying offset, and an high limit
268
-		// specifying only offset without limit will not work by underlying lib
269
-		$results = $mapper->getList(1, 999);
270
-		$this->assertCount(count($data) - 1, $results);
271
-
272
-		// get first 2 entries by limit, but not offset
273
-		$results = $mapper->getList(0, 2);
274
-		$this->assertCount(2, $results);
275
-
276
-		// get 2nd entry by specifying both offset and limit
277
-		$results = $mapper->getList(1, 1);
278
-		$this->assertCount(1, $results);
279
-	}
280
-
281
-	public function testGetListOfIdsByDn(): void {
282
-		/** @var AbstractMapping $mapper */
283
-		[$mapper,] = $this->initTest();
284
-
285
-		$listOfDNs = [];
286
-		for ($i = 0; $i < 66640; $i++) {
287
-			// Postgres has a limit of 65535 values in a single IN list
288
-			$name = 'as_' . $i;
289
-			$dn = 'uid=' . $name . ',dc=example,dc=org';
290
-			$listOfDNs[] = $dn;
291
-			if ($i % 20 === 0) {
292
-				$mapper->map($dn, $name, 'fake-uuid-' . $i);
293
-			}
294
-		}
295
-
296
-		$result = $mapper->getListOfIdsByDn($listOfDNs);
297
-		$this->assertCount(66640 / 20, $result);
298
-	}
20
+    private ICacheFactory&MockObject $cacheFactoryMock;
21
+
22
+    private IAppConfig&MockObject $configMock;
23
+
24
+    abstract public function getMapper(IDBConnection $dbMock, ICacheFactory $cacheFactory, IAppConfig $appConfig): AbstractMapping;
25
+
26
+    protected function setUp(): void {
27
+        $this->cacheFactoryMock = $this->createMock(ICacheFactory::class);
28
+        $this->configMock = $this->createMock(IAppConfig::class);
29
+    }
30
+
31
+    /**
32
+     * kiss test on isColNameValid
33
+     */
34
+    public function testIsColNameValid(): void {
35
+        $dbMock = $this->createMock(IDBConnection::class);
36
+        $mapper = $this->getMapper($dbMock, $this->cacheFactoryMock, $this->configMock);
37
+
38
+        $this->assertTrue($mapper->isColNameValid('ldap_dn'));
39
+        $this->assertFalse($mapper->isColNameValid('foobar'));
40
+    }
41
+
42
+    /**
43
+     * returns an array of test entries with dn, name and uuid as keys
44
+     * @return array
45
+     */
46
+    protected static function getTestData(): array {
47
+        return [
48
+            [
49
+                'dn' => 'uid=foobar,dc=example,dc=org',
50
+                'name' => 'Foobar',
51
+                'uuid' => '1111-AAAA-1234-CDEF',
52
+            ],
53
+            [
54
+                'dn' => 'uid=barfoo,dc=example,dc=org',
55
+                'name' => 'Barfoo',
56
+                'uuid' => '2222-BBBB-1234-CDEF',
57
+            ],
58
+            [
59
+                'dn' => 'uid=barabara,dc=example,dc=org',
60
+                'name' => 'BaraBara',
61
+                'uuid' => '3333-CCCC-1234-CDEF',
62
+            ]
63
+        ];
64
+    }
65
+
66
+    /**
67
+     * calls map() on the given mapper and asserts result for true
68
+     * @param AbstractMapping $mapper
69
+     * @param array $data
70
+     */
71
+    protected function mapEntries(AbstractMapping $mapper, array $data): void {
72
+        foreach ($data as $entry) {
73
+            $done = $mapper->map($entry['dn'], $entry['name'], $entry['uuid']);
74
+            $this->assertTrue($done);
75
+        }
76
+    }
77
+
78
+    /**
79
+     * initializes environment for a test run and returns an array with
80
+     * test objects. Preparing environment means that all mappings are cleared
81
+     * first and then filled with test entries.
82
+     * @return array 0 = \OCA\User_LDAP\Mapping\AbstractMapping, 1 = array of
83
+     *               users or groups
84
+     */
85
+    private function initTest(): array {
86
+        $dbc = Server::get(IDBConnection::class);
87
+        $mapper = $this->getMapper($dbc, $this->cacheFactoryMock, $this->configMock);
88
+        $data = $this->getTestData();
89
+        // make sure DB is pristine, then fill it with test entries
90
+        $mapper->clear();
91
+        $this->mapEntries($mapper, $data);
92
+
93
+        return [$mapper, $data];
94
+    }
95
+
96
+    /**
97
+     * tests map() method with input that should result in not-mapping.
98
+     * Hint: successful mapping is tested inherently with mapEntries().
99
+     */
100
+    public function testMap(): void {
101
+        [$mapper, $data] = $this->initTest();
102
+
103
+        // test that mapping will not happen when it shall not
104
+        $tooLongDN = 'uid=joann,ou=Secret Small Specialized Department,ou=Some Tremendously Important Department,ou=Another Very Important Department,ou=Pretty Meaningful Derpartment,ou=Quite Broad And General Department,ou=The Topmost Department,dc=hugelysuccessfulcompany,dc=com';
105
+        $paramKeys = ['', 'dn', 'name', 'uuid', $tooLongDN];
106
+        foreach ($paramKeys as $key) {
107
+            $failEntry = $data[0];
108
+            if (!empty($key)) {
109
+                $failEntry[$key] = 'do-not-get-mapped';
110
+            }
111
+            $isMapped = $mapper->map($failEntry['dn'], $failEntry['name'], $failEntry['uuid']);
112
+            $this->assertFalse($isMapped);
113
+        }
114
+    }
115
+
116
+    /**
117
+     * tests unmap() for both successful and unsuccessful removing of
118
+     * mapping entries
119
+     */
120
+    public function testUnmap(): void {
121
+        [$mapper, $data] = $this->initTest();
122
+
123
+        foreach ($data as $entry) {
124
+            $fdnBefore = $mapper->getDNByName($entry['name']);
125
+            $result = $mapper->unmap($entry['name']);
126
+            $fdnAfter = $mapper->getDNByName($entry['name']);
127
+            $this->assertTrue($result);
128
+            $this->assertSame($fdnBefore, $entry['dn']);
129
+            $this->assertFalse($fdnAfter);
130
+        }
131
+
132
+        $result = $mapper->unmap('notAnEntry');
133
+        $this->assertFalse($result);
134
+    }
135
+
136
+    /**
137
+     * tests getDNByName(), getNameByDN() and getNameByUUID() for successful
138
+     * and unsuccessful requests.
139
+     */
140
+    public function testGetMethods(): void {
141
+        [$mapper, $data] = $this->initTest();
142
+
143
+        foreach ($data as $entry) {
144
+            $fdn = $mapper->getDNByName($entry['name']);
145
+            $this->assertSame($fdn, $entry['dn']);
146
+        }
147
+        $fdn = $mapper->getDNByName('nosuchname');
148
+        $this->assertFalse($fdn);
149
+
150
+        foreach ($data as $entry) {
151
+            $name = $mapper->getNameByDN($entry['dn']);
152
+            $this->assertSame($name, $entry['name']);
153
+        }
154
+        $name = $mapper->getNameByDN('nosuchdn');
155
+        $this->assertFalse($name);
156
+
157
+        foreach ($data as $entry) {
158
+            $name = $mapper->getNameByUUID($entry['uuid']);
159
+            $this->assertSame($name, $entry['name']);
160
+        }
161
+        $name = $mapper->getNameByUUID('nosuchuuid');
162
+        $this->assertFalse($name);
163
+    }
164
+
165
+    /**
166
+     * tests getNamesBySearch() for successful and unsuccessful requests.
167
+     */
168
+    public function testSearch(): void {
169
+        [$mapper,] = $this->initTest();
170
+
171
+        $names = $mapper->getNamesBySearch('oo', '%', '%');
172
+        $this->assertIsArray($names);
173
+        $this->assertSame(2, count($names));
174
+        $this->assertContains('Foobar', $names);
175
+        $this->assertContains('Barfoo', $names);
176
+        $names = $mapper->getNamesBySearch('nada');
177
+        $this->assertIsArray($names);
178
+        $this->assertCount(0, $names);
179
+    }
180
+
181
+    /**
182
+     * tests setDNbyUUID() for successful and unsuccessful update.
183
+     */
184
+    public function testSetDNMethod(): void {
185
+        [$mapper, $data] = $this->initTest();
186
+
187
+        $newDN = 'uid=modified,dc=example,dc=org';
188
+        $done = $mapper->setDNbyUUID($newDN, $data[0]['uuid']);
189
+        $this->assertTrue($done);
190
+        $fdn = $mapper->getDNByName($data[0]['name']);
191
+        $this->assertSame($fdn, $newDN);
192
+
193
+        $newDN = 'uid=notme,dc=example,dc=org';
194
+        $done = $mapper->setDNbyUUID($newDN, 'iamnothere');
195
+        $this->assertFalse($done);
196
+        $name = $mapper->getNameByDN($newDN);
197
+        $this->assertFalse($name);
198
+    }
199
+
200
+    /**
201
+     * tests setUUIDbyDN() for successful and unsuccessful update.
202
+     */
203
+    public function testSetUUIDMethod(): void {
204
+        /** @var AbstractMapping $mapper */
205
+        [$mapper, $data] = $this->initTest();
206
+
207
+        $newUUID = 'ABC737-DEF754';
208
+
209
+        $done = $mapper->setUUIDbyDN($newUUID, 'uid=notme,dc=example,dc=org');
210
+        $this->assertFalse($done);
211
+        $name = $mapper->getNameByUUID($newUUID);
212
+        $this->assertFalse($name);
213
+
214
+        $done = $mapper->setUUIDbyDN($newUUID, $data[0]['dn']);
215
+        $this->assertTrue($done);
216
+        $uuid = $mapper->getUUIDByDN($data[0]['dn']);
217
+        $this->assertSame($uuid, $newUUID);
218
+    }
219
+
220
+    /**
221
+     * tests clear() for successful update.
222
+     */
223
+    public function testClear(): void {
224
+        [$mapper, $data] = $this->initTest();
225
+
226
+        $done = $mapper->clear();
227
+        $this->assertTrue($done);
228
+        foreach ($data as $entry) {
229
+            $name = $mapper->getNameByUUID($entry['uuid']);
230
+            $this->assertFalse($name);
231
+        }
232
+    }
233
+
234
+    /**
235
+     * tests clear() for successful update.
236
+     */
237
+    public function testClearCb(): void {
238
+        [$mapper, $data] = $this->initTest();
239
+
240
+        $callbackCalls = 0;
241
+        $test = $this;
242
+
243
+        $callback = function (string $id) use ($test, &$callbackCalls): void {
244
+            $test->assertTrue(trim($id) !== '');
245
+            $callbackCalls++;
246
+        };
247
+
248
+        $done = $mapper->clearCb($callback, $callback);
249
+        $this->assertTrue($done);
250
+        $this->assertSame(count($data) * 2, $callbackCalls);
251
+        foreach ($data as $entry) {
252
+            $name = $mapper->getNameByUUID($entry['uuid']);
253
+            $this->assertFalse($name);
254
+        }
255
+    }
256
+
257
+    /**
258
+     * tests getList() method
259
+     */
260
+    public function testList(): void {
261
+        [$mapper, $data] = $this->initTest();
262
+
263
+        // get all entries without specifying offset or limit
264
+        $results = $mapper->getList();
265
+        $this->assertCount(3, $results);
266
+
267
+        // get all-1 entries by specifying offset, and an high limit
268
+        // specifying only offset without limit will not work by underlying lib
269
+        $results = $mapper->getList(1, 999);
270
+        $this->assertCount(count($data) - 1, $results);
271
+
272
+        // get first 2 entries by limit, but not offset
273
+        $results = $mapper->getList(0, 2);
274
+        $this->assertCount(2, $results);
275
+
276
+        // get 2nd entry by specifying both offset and limit
277
+        $results = $mapper->getList(1, 1);
278
+        $this->assertCount(1, $results);
279
+    }
280
+
281
+    public function testGetListOfIdsByDn(): void {
282
+        /** @var AbstractMapping $mapper */
283
+        [$mapper,] = $this->initTest();
284
+
285
+        $listOfDNs = [];
286
+        for ($i = 0; $i < 66640; $i++) {
287
+            // Postgres has a limit of 65535 values in a single IN list
288
+            $name = 'as_' . $i;
289
+            $dn = 'uid=' . $name . ',dc=example,dc=org';
290
+            $listOfDNs[] = $dn;
291
+            if ($i % 20 === 0) {
292
+                $mapper->map($dn, $name, 'fake-uuid-' . $i);
293
+            }
294
+        }
295
+
296
+        $result = $mapper->getListOfIdsByDn($listOfDNs);
297
+        $this->assertCount(66640 / 20, $result);
298
+    }
299 299
 }
Please login to merge, or discard this patch.