Passed
Push — master ( 84a353...212138 )
by Blizzz
12:21 queued 10s
created

AbstractMapping::getDnByUUID()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Aaron Wood <[email protected]>
6
 * @author Arthur Schiwon <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 *
11
 * @license AGPL-3.0
12
 *
13
 * This code is free software: you can redistribute it and/or modify
14
 * it under the terms of the GNU Affero General Public License, version 3,
15
 * as published by the Free Software Foundation.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License, version 3,
23
 * along with this program. If not, see <http://www.gnu.org/licenses/>
24
 *
25
 */
26
27
namespace OCA\User_LDAP\Mapping;
28
29
use OC\DB\QueryBuilder\QueryBuilder;
30
31
/**
32
 * Class AbstractMapping
33
 *
34
 * @package OCA\User_LDAP\Mapping
35
 */
36
abstract class AbstractMapping {
37
	/**
38
	 * @var \OCP\IDBConnection $dbc
39
	 */
40
	protected $dbc;
41
42
	/**
43
	 * returns the DB table name which holds the mappings
44
	 *
45
	 * @return string
46
	 */
47
	abstract protected function getTableName(bool $includePrefix = true);
48
49
	/**
50
	 * @param \OCP\IDBConnection $dbc
51
	 */
52
	public function __construct(\OCP\IDBConnection $dbc) {
53
		$this->dbc = $dbc;
54
	}
55
56
	/** @var array caches Names (value) by DN (key) */
57
	protected $cache = [];
58
59
	/**
60
	 * checks whether a provided string represents an existing table col
61
	 *
62
	 * @param string $col
63
	 * @return bool
64
	 */
65
	public function isColNameValid($col) {
66
		switch ($col) {
67
			case 'ldap_dn':
68
			case 'owncloud_name':
69
			case 'directory_uuid':
70
				return true;
71
			default:
72
				return false;
73
		}
74
	}
75
76
	/**
77
	 * Gets the value of one column based on a provided value of another column
78
	 *
79
	 * @param string $fetchCol
80
	 * @param string $compareCol
81
	 * @param string $search
82
	 * @return string|false
83
	 * @throws \Exception
84
	 */
85
	protected function getXbyY($fetchCol, $compareCol, $search) {
86
		if (!$this->isColNameValid($fetchCol)) {
87
			//this is used internally only, but we don't want to risk
88
			//having SQL injection at all.
89
			throw new \Exception('Invalid Column Name');
90
		}
91
		$query = $this->dbc->prepare('
92
			SELECT `' . $fetchCol . '`
93
			FROM `' . $this->getTableName() . '`
94
			WHERE `' . $compareCol . '` = ?
95
		');
96
97
		$res = $query->execute([$search]);
98
		if ($res !== false) {
99
			return $query->fetchColumn();
100
		}
101
102
		return false;
103
	}
104
105
	/**
106
	 * Performs a DELETE or UPDATE query to the database.
107
	 *
108
	 * @param \Doctrine\DBAL\Driver\Statement $query
109
	 * @param array $parameters
110
	 * @return bool true if at least one row was modified, false otherwise
111
	 */
112
	protected function modify($query, $parameters) {
113
		$result = $query->execute($parameters);
114
		return ($result === true && $query->rowCount() > 0);
115
	}
116
117
	/**
118
	 * Gets the LDAP DN based on the provided name.
119
	 * Replaces Access::ocname2dn
120
	 *
121
	 * @param string $name
122
	 * @return string|false
123
	 */
124
	public function getDNByName($name) {
125
		$dn = array_search($name, $this->cache);
126
		if ($dn === false) {
127
			$dn = $this->getXbyY('ldap_dn', 'owncloud_name', $name);
128
			$this->cache[$dn] = $name;
129
		}
130
		return $dn;
131
	}
132
133
	/**
134
	 * Updates the DN based on the given UUID
135
	 *
136
	 * @param string $fdn
137
	 * @param string $uuid
138
	 * @return bool
139
	 */
140
	public function setDNbyUUID($fdn, $uuid) {
141
		$oldDn = $this->getDnByUUID($uuid);
142
		$query = $this->dbc->prepare('
143
			UPDATE `' . $this->getTableName() . '`
144
			SET `ldap_dn` = ?
145
			WHERE `directory_uuid` = ?
146
		');
147
148
		$r = $this->modify($query, [$fdn, $uuid]);
149
150
		if ($r && is_string($oldDn) && isset($this->cache[$oldDn])) {
151
			$this->cache[$fdn] = $this->cache[$oldDn];
152
			unset($this->cache[$oldDn]);
153
		}
154
155
		return $r;
156
	}
157
158
	/**
159
	 * Updates the UUID based on the given DN
160
	 *
161
	 * required by Migration/UUIDFix
162
	 *
163
	 * @param $uuid
164
	 * @param $fdn
165
	 * @return bool
166
	 */
167
	public function setUUIDbyDN($uuid, $fdn) {
168
		$query = $this->dbc->prepare('
169
			UPDATE `' . $this->getTableName() . '`
170
			SET `directory_uuid` = ?
171
			WHERE `ldap_dn` = ?
172
		');
173
174
		unset($this->cache[$fdn]);
175
176
		return $this->modify($query, [$uuid, $fdn]);
177
	}
178
179
	/**
180
	 * Gets the name based on the provided LDAP DN.
181
	 *
182
	 * @param string $fdn
183
	 * @return string|false
184
	 */
185
	public function getNameByDN($fdn) {
186
		if (!isset($this->cache[$fdn])) {
187
			$this->cache[$fdn] = $this->getXbyY('owncloud_name', 'ldap_dn', $fdn);
188
		}
189
		return $this->cache[$fdn];
190
	}
191
192
	public function getListOfIdsByDn(array $fdns): array {
193
		$qb = $this->dbc->getQueryBuilder();
194
		$qb->select('owncloud_name', 'ldap_dn')
195
			->from($this->getTableName(false))
196
			->where($qb->expr()->in('ldap_dn', $qb->createNamedParameter($fdns, QueryBuilder::PARAM_STR_ARRAY)));
197
		$stmt = $qb->execute();
198
199
		$results = $stmt->fetchAll(\Doctrine\DBAL\FetchMode::ASSOCIATIVE);
200
		foreach ($results as $key => $entry) {
201
			unset($results[$key]);
202
			$results[$entry['ldap_dn']] = $entry['owncloud_name'];
203
			$this->cache[$entry['ldap_dn']] = $entry['owncloud_name'];
204
		}
205
206
		return $results;
207
	}
208
209
	/**
210
	 * Searches mapped names by the giving string in the name column
211
	 *
212
	 * @param string $search
213
	 * @param string $prefixMatch
214
	 * @param string $postfixMatch
215
	 * @return string[]
216
	 */
217
	public function getNamesBySearch($search, $prefixMatch = "", $postfixMatch = "") {
218
		$query = $this->dbc->prepare('
219
			SELECT `owncloud_name`
220
			FROM `' . $this->getTableName() . '`
221
			WHERE `owncloud_name` LIKE ?
222
		');
223
224
		$res = $query->execute([$prefixMatch . $this->dbc->escapeLikeParameter($search) . $postfixMatch]);
225
		$names = [];
226
		if ($res !== false) {
227
			while ($row = $query->fetch()) {
228
				$names[] = $row['owncloud_name'];
229
			}
230
		}
231
		return $names;
232
	}
233
234
	/**
235
	 * Gets the name based on the provided LDAP UUID.
236
	 *
237
	 * @param string $uuid
238
	 * @return string|false
239
	 */
240
	public function getNameByUUID($uuid) {
241
		return $this->getXbyY('owncloud_name', 'directory_uuid', $uuid);
242
	}
243
244
	public function getDnByUUID($uuid) {
245
		return $this->getXbyY('ldap_dn', 'directory_uuid', $uuid);
246
	}
247
248
	/**
249
	 * Gets the UUID based on the provided LDAP DN
250
	 *
251
	 * @param string $dn
252
	 * @return false|string
253
	 * @throws \Exception
254
	 */
255
	public function getUUIDByDN($dn) {
256
		return $this->getXbyY('directory_uuid', 'ldap_dn', $dn);
257
	}
258
259
	/**
260
	 * gets a piece of the mapping list
261
	 *
262
	 * @param int $offset
263
	 * @param int $limit
264
	 * @return array
265
	 */
266
	public function getList($offset = null, $limit = null) {
267
		$query = $this->dbc->prepare('
268
			SELECT
269
				`ldap_dn` AS `dn`,
270
				`owncloud_name` AS `name`,
271
				`directory_uuid` AS `uuid`
272
			FROM `' . $this->getTableName() . '`',
273
			$limit,
274
			$offset
275
		);
276
277
		$query->execute();
278
		return $query->fetchAll();
279
	}
280
281
	/**
282
	 * attempts to map the given entry
283
	 *
284
	 * @param string $fdn fully distinguished name (from LDAP)
285
	 * @param string $name
286
	 * @param string $uuid a unique identifier as used in LDAP
287
	 * @return bool
288
	 */
289
	public function map($fdn, $name, $uuid) {
290
		if (mb_strlen($fdn) > 255) {
291
			\OC::$server->getLogger()->error(
292
				'Cannot map, because the DN exceeds 255 characters: {dn}',
293
				[
294
					'app' => 'user_ldap',
295
					'dn' => $fdn,
296
				]
297
			);
298
			return false;
299
		}
300
301
		$row = [
302
			'ldap_dn' => $fdn,
303
			'owncloud_name' => $name,
304
			'directory_uuid' => $uuid
305
		];
306
307
		try {
308
			$result = $this->dbc->insertIfNotExist($this->getTableName(), $row);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\IDBConnection::insertIfNotExist() has been deprecated: 15.0.0 - use unique index and "try { $db->insert() } catch (UniqueConstraintViolationException $e) {}" instead, because it is more reliable and does not have the risk for deadlocks - see https://github.com/nextcloud/server/pull/12371 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

308
			$result = /** @scrutinizer ignore-deprecated */ $this->dbc->insertIfNotExist($this->getTableName(), $row);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
309
			if ((bool)$result === true) {
310
				$this->cache[$fdn] = $name;
311
			}
312
			// insertIfNotExist returns values as int
313
			return (bool)$result;
314
		} catch (\Exception $e) {
315
			return false;
316
		}
317
	}
318
319
	/**
320
	 * removes a mapping based on the owncloud_name of the entry
321
	 *
322
	 * @param string $name
323
	 * @return bool
324
	 */
325
	public function unmap($name) {
326
		$query = $this->dbc->prepare('
327
			DELETE FROM `' . $this->getTableName() . '`
328
			WHERE `owncloud_name` = ?');
329
330
		return $this->modify($query, [$name]);
331
	}
332
333
	/**
334
	 * Truncate's the mapping table
335
	 *
336
	 * @return bool
337
	 */
338
	public function clear() {
339
		$sql = $this->dbc
340
			->getDatabasePlatform()
341
			->getTruncateTableSQL('`' . $this->getTableName() . '`');
342
		return $this->dbc->prepare($sql)->execute();
343
	}
344
345
	/**
346
	 * clears the mapping table one by one and executing a callback with
347
	 * each row's id (=owncloud_name col)
348
	 *
349
	 * @param callable $preCallback
350
	 * @param callable $postCallback
351
	 * @return bool true on success, false when at least one row was not
352
	 * deleted
353
	 */
354
	public function clearCb(callable $preCallback, callable $postCallback): bool {
355
		$picker = $this->dbc->getQueryBuilder();
356
		$picker->select('owncloud_name')
357
			->from($this->getTableName());
358
		$cursor = $picker->execute();
359
		$result = true;
360
		while ($id = $cursor->fetchColumn(0)) {
361
			$preCallback($id);
362
			if ($isUnmapped = $this->unmap($id)) {
363
				$postCallback($id);
364
			}
365
			$result &= $isUnmapped;
366
		}
367
		$cursor->closeCursor();
368
		return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result could return the type integer which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
369
	}
370
371
	/**
372
	 * returns the number of entries in the mappings table
373
	 *
374
	 * @return int
375
	 */
376
	public function count() {
377
		$qb = $this->dbc->getQueryBuilder();
378
		$query = $qb->select($qb->func()->count('ldap_dn'))
379
			->from($this->getTableName());
380
		$res = $query->execute();
381
		$count = $res->fetchColumn();
382
		$res->closeCursor();
383
		return (int)$count;
384
	}
385
}
386