Passed
Push — master ( 645109...008e6d )
by Christoph
12:14 queued 12s
created
apps/dav/lib/CardDAV/ImageExportPlugin.php 1 patch
Indentation   +81 added lines, -81 removed lines patch added patch discarded remove patch
@@ -32,85 +32,85 @@
 block discarded – undo
32 32
 
33 33
 class ImageExportPlugin extends ServerPlugin {
34 34
 
35
-	/** @var Server */
36
-	protected $server;
37
-	/** @var PhotoCache */
38
-	private $cache;
39
-
40
-	/**
41
-	 * ImageExportPlugin constructor.
42
-	 *
43
-	 * @param PhotoCache $cache
44
-	 */
45
-	public function __construct(PhotoCache $cache) {
46
-		$this->cache = $cache;
47
-	}
48
-
49
-	/**
50
-	 * Initializes the plugin and registers event handlers
51
-	 *
52
-	 * @param Server $server
53
-	 * @return void
54
-	 */
55
-	public function initialize(Server $server) {
56
-		$this->server = $server;
57
-		$this->server->on('method:GET', [$this, 'httpGet'], 90);
58
-	}
59
-
60
-	/**
61
-	 * Intercepts GET requests on addressbook urls ending with ?photo.
62
-	 *
63
-	 * @param RequestInterface $request
64
-	 * @param ResponseInterface $response
65
-	 * @return bool
66
-	 */
67
-	public function httpGet(RequestInterface $request, ResponseInterface $response) {
68
-
69
-		$queryParams = $request->getQueryParameters();
70
-		// TODO: in addition to photo we should also add logo some point in time
71
-		if (!array_key_exists('photo', $queryParams)) {
72
-			return true;
73
-		}
74
-
75
-		$size = isset($queryParams['size']) ? (int)$queryParams['size'] : -1;
76
-
77
-		$path = $request->getPath();
78
-		$node = $this->server->tree->getNodeForPath($path);
79
-
80
-		if (!($node instanceof Card)) {
81
-			return true;
82
-		}
83
-
84
-		$this->server->transactionType = 'carddav-image-export';
85
-
86
-		// Checking ACL, if available.
87
-		if ($aclPlugin = $this->server->getPlugin('acl')) {
88
-			/** @var \Sabre\DAVACL\Plugin $aclPlugin */
89
-			$aclPlugin->checkPrivileges($path, '{DAV:}read');
90
-		}
91
-
92
-		// Fetch addressbook
93
-		$addressbookpath = explode('/', $path);
94
-		array_pop($addressbookpath);
95
-		$addressbookpath = implode('/', $addressbookpath);
96
-		/** @var AddressBook $addressbook */
97
-		$addressbook = $this->server->tree->getNodeForPath($addressbookpath);
98
-
99
-		$response->setHeader('Cache-Control', 'private, max-age=3600, must-revalidate');
100
-		$response->setHeader('Etag', $node->getETag());
101
-		$response->setHeader('Pragma', 'public');
102
-
103
-		try {
104
-			$file = $this->cache->get($addressbook->getResourceId(), $node->getName(), $size, $node);
105
-			$response->setHeader('Content-Type', $file->getMimeType());
106
-			$response->setHeader('Content-Disposition', 'attachment');
107
-			$response->setStatus(200);
108
-
109
-			$response->setBody($file->getContent());
110
-		} catch (NotFoundException $e) {
111
-			$response->setStatus(404);
112
-		}
113
-
114
-		return false;
115
-	}
35
+    /** @var Server */
36
+    protected $server;
37
+    /** @var PhotoCache */
38
+    private $cache;
39
+
40
+    /**
41
+     * ImageExportPlugin constructor.
42
+     *
43
+     * @param PhotoCache $cache
44
+     */
45
+    public function __construct(PhotoCache $cache) {
46
+        $this->cache = $cache;
47
+    }
48
+
49
+    /**
50
+     * Initializes the plugin and registers event handlers
51
+     *
52
+     * @param Server $server
53
+     * @return void
54
+     */
55
+    public function initialize(Server $server) {
56
+        $this->server = $server;
57
+        $this->server->on('method:GET', [$this, 'httpGet'], 90);
58
+    }
59
+
60
+    /**
61
+     * Intercepts GET requests on addressbook urls ending with ?photo.
62
+     *
63
+     * @param RequestInterface $request
64
+     * @param ResponseInterface $response
65
+     * @return bool
66
+     */
67
+    public function httpGet(RequestInterface $request, ResponseInterface $response) {
68
+
69
+        $queryParams = $request->getQueryParameters();
70
+        // TODO: in addition to photo we should also add logo some point in time
71
+        if (!array_key_exists('photo', $queryParams)) {
72
+            return true;
73
+        }
74
+
75
+        $size = isset($queryParams['size']) ? (int)$queryParams['size'] : -1;
76
+
77
+        $path = $request->getPath();
78
+        $node = $this->server->tree->getNodeForPath($path);
79
+
80
+        if (!($node instanceof Card)) {
81
+            return true;
82
+        }
83
+
84
+        $this->server->transactionType = 'carddav-image-export';
85
+
86
+        // Checking ACL, if available.
87
+        if ($aclPlugin = $this->server->getPlugin('acl')) {
88
+            /** @var \Sabre\DAVACL\Plugin $aclPlugin */
89
+            $aclPlugin->checkPrivileges($path, '{DAV:}read');
90
+        }
91
+
92
+        // Fetch addressbook
93
+        $addressbookpath = explode('/', $path);
94
+        array_pop($addressbookpath);
95
+        $addressbookpath = implode('/', $addressbookpath);
96
+        /** @var AddressBook $addressbook */
97
+        $addressbook = $this->server->tree->getNodeForPath($addressbookpath);
98
+
99
+        $response->setHeader('Cache-Control', 'private, max-age=3600, must-revalidate');
100
+        $response->setHeader('Etag', $node->getETag());
101
+        $response->setHeader('Pragma', 'public');
102
+
103
+        try {
104
+            $file = $this->cache->get($addressbook->getResourceId(), $node->getName(), $size, $node);
105
+            $response->setHeader('Content-Type', $file->getMimeType());
106
+            $response->setHeader('Content-Disposition', 'attachment');
107
+            $response->setStatus(200);
108
+
109
+            $response->setBody($file->getContent());
110
+        } catch (NotFoundException $e) {
111
+            $response->setStatus(404);
112
+        }
113
+
114
+        return false;
115
+    }
116 116
 }
Please login to merge, or discard this patch.
apps/dav/lib/CardDAV/CardDavBackend.php 1 patch
Indentation   +1118 added lines, -1118 removed lines patch added patch discarded remove patch
@@ -56,1122 +56,1122 @@
 block discarded – undo
56 56
 
57 57
 class CardDavBackend implements BackendInterface, SyncSupport {
58 58
 
59
-	const PERSONAL_ADDRESSBOOK_URI = 'contacts';
60
-	const PERSONAL_ADDRESSBOOK_NAME = 'Contacts';
61
-
62
-	/** @var Principal */
63
-	private $principalBackend;
64
-
65
-	/** @var string */
66
-	private $dbCardsTable = 'cards';
67
-
68
-	/** @var string */
69
-	private $dbCardsPropertiesTable = 'cards_properties';
70
-
71
-	/** @var IDBConnection */
72
-	private $db;
73
-
74
-	/** @var Backend */
75
-	private $sharingBackend;
76
-
77
-	/** @var array properties to index */
78
-	public static $indexProperties = [
79
-		'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
80
-		'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD'];
81
-
82
-	/**
83
-	 * @var string[] Map of uid => display name
84
-	 */
85
-	protected $userDisplayNames;
86
-
87
-	/** @var IUserManager */
88
-	private $userManager;
89
-
90
-	/** @var EventDispatcherInterface */
91
-	private $dispatcher;
92
-
93
-	/**
94
-	 * CardDavBackend constructor.
95
-	 *
96
-	 * @param IDBConnection $db
97
-	 * @param Principal $principalBackend
98
-	 * @param IUserManager $userManager
99
-	 * @param IGroupManager $groupManager
100
-	 * @param EventDispatcherInterface $dispatcher
101
-	 */
102
-	public function __construct(IDBConnection $db,
103
-								Principal $principalBackend,
104
-								IUserManager $userManager,
105
-								IGroupManager $groupManager,
106
-								EventDispatcherInterface $dispatcher) {
107
-		$this->db = $db;
108
-		$this->principalBackend = $principalBackend;
109
-		$this->userManager = $userManager;
110
-		$this->dispatcher = $dispatcher;
111
-		$this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'addressbook');
112
-	}
113
-
114
-	/**
115
-	 * Return the number of address books for a principal
116
-	 *
117
-	 * @param $principalUri
118
-	 * @return int
119
-	 */
120
-	public function getAddressBooksForUserCount($principalUri) {
121
-		$principalUri = $this->convertPrincipal($principalUri, true);
122
-		$query = $this->db->getQueryBuilder();
123
-		$query->select($query->func()->count('*'))
124
-			->from('addressbooks')
125
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
126
-
127
-		return (int)$query->execute()->fetchColumn();
128
-	}
129
-
130
-	/**
131
-	 * Returns the list of address books for a specific user.
132
-	 *
133
-	 * Every addressbook should have the following properties:
134
-	 *   id - an arbitrary unique id
135
-	 *   uri - the 'basename' part of the url
136
-	 *   principaluri - Same as the passed parameter
137
-	 *
138
-	 * Any additional clark-notation property may be passed besides this. Some
139
-	 * common ones are :
140
-	 *   {DAV:}displayname
141
-	 *   {urn:ietf:params:xml:ns:carddav}addressbook-description
142
-	 *   {http://calendarserver.org/ns/}getctag
143
-	 *
144
-	 * @param string $principalUri
145
-	 * @return array
146
-	 */
147
-	function getAddressBooksForUser($principalUri) {
148
-		$principalUriOriginal = $principalUri;
149
-		$principalUri = $this->convertPrincipal($principalUri, true);
150
-		$query = $this->db->getQueryBuilder();
151
-		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
152
-			->from('addressbooks')
153
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
154
-
155
-		$addressBooks = [];
156
-
157
-		$result = $query->execute();
158
-		while($row = $result->fetch()) {
159
-			$addressBooks[$row['id']] = [
160
-				'id'  => $row['id'],
161
-				'uri' => $row['uri'],
162
-				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
163
-				'{DAV:}displayname' => $row['displayname'],
164
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
165
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
166
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
167
-			];
168
-
169
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
170
-		}
171
-		$result->closeCursor();
172
-
173
-		// query for shared addressbooks
174
-		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
175
-		$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
176
-
177
-		$principals = array_map(function ($principal) {
178
-			return urldecode($principal);
179
-		}, $principals);
180
-		$principals[]= $principalUri;
181
-
182
-		$query = $this->db->getQueryBuilder();
183
-		$result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
184
-			->from('dav_shares', 's')
185
-			->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
186
-			->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
187
-			->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
188
-			->setParameter('type', 'addressbook')
189
-			->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
190
-			->execute();
191
-
192
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
193
-		while($row = $result->fetch()) {
194
-			if ($row['principaluri'] === $principalUri) {
195
-				continue;
196
-			}
197
-
198
-			$readOnly = (int) $row['access'] === Backend::ACCESS_READ;
199
-			if (isset($addressBooks[$row['id']])) {
200
-				if ($readOnly) {
201
-					// New share can not have more permissions then the old one.
202
-					continue;
203
-				}
204
-				if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
205
-					$addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
206
-					// Old share is already read-write, no more permissions can be gained
207
-					continue;
208
-				}
209
-			}
210
-
211
-			list(, $name) = \Sabre\Uri\split($row['principaluri']);
212
-			$uri = $row['uri'] . '_shared_by_' . $name;
213
-			$displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
214
-
215
-			$addressBooks[$row['id']] = [
216
-				'id'  => $row['id'],
217
-				'uri' => $uri,
218
-				'principaluri' => $principalUriOriginal,
219
-				'{DAV:}displayname' => $displayName,
220
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
221
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
222
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
223
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
224
-				$readOnlyPropertyName => $readOnly,
225
-			];
226
-
227
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
228
-		}
229
-		$result->closeCursor();
230
-
231
-		return array_values($addressBooks);
232
-	}
233
-
234
-	public function getUsersOwnAddressBooks($principalUri) {
235
-		$principalUri = $this->convertPrincipal($principalUri, true);
236
-		$query = $this->db->getQueryBuilder();
237
-		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
238
-			  ->from('addressbooks')
239
-			  ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
240
-
241
-		$addressBooks = [];
242
-
243
-		$result = $query->execute();
244
-		while($row = $result->fetch()) {
245
-			$addressBooks[$row['id']] = [
246
-				'id'  => $row['id'],
247
-				'uri' => $row['uri'],
248
-				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
249
-				'{DAV:}displayname' => $row['displayname'],
250
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
251
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
252
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
253
-			];
254
-
255
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
256
-		}
257
-		$result->closeCursor();
258
-
259
-		return array_values($addressBooks);
260
-	}
261
-
262
-	private function getUserDisplayName($uid) {
263
-		if (!isset($this->userDisplayNames[$uid])) {
264
-			$user = $this->userManager->get($uid);
265
-
266
-			if ($user instanceof IUser) {
267
-				$this->userDisplayNames[$uid] = $user->getDisplayName();
268
-			} else {
269
-				$this->userDisplayNames[$uid] = $uid;
270
-			}
271
-		}
272
-
273
-		return $this->userDisplayNames[$uid];
274
-	}
275
-
276
-	/**
277
-	 * @param int $addressBookId
278
-	 */
279
-	public function getAddressBookById($addressBookId) {
280
-		$query = $this->db->getQueryBuilder();
281
-		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
282
-			->from('addressbooks')
283
-			->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
284
-			->execute();
285
-
286
-		$row = $result->fetch();
287
-		$result->closeCursor();
288
-		if ($row === false) {
289
-			return null;
290
-		}
291
-
292
-		$addressBook = [
293
-			'id'  => $row['id'],
294
-			'uri' => $row['uri'],
295
-			'principaluri' => $row['principaluri'],
296
-			'{DAV:}displayname' => $row['displayname'],
297
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
298
-			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
299
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
300
-		];
301
-
302
-		$this->addOwnerPrincipal($addressBook);
303
-
304
-		return $addressBook;
305
-	}
306
-
307
-	/**
308
-	 * @param $addressBookUri
309
-	 * @return array|null
310
-	 */
311
-	public function getAddressBooksByUri($principal, $addressBookUri) {
312
-		$query = $this->db->getQueryBuilder();
313
-		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
314
-			->from('addressbooks')
315
-			->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
316
-			->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
317
-			->setMaxResults(1)
318
-			->execute();
319
-
320
-		$row = $result->fetch();
321
-		$result->closeCursor();
322
-		if ($row === false) {
323
-			return null;
324
-		}
325
-
326
-		$addressBook = [
327
-			'id'  => $row['id'],
328
-			'uri' => $row['uri'],
329
-			'principaluri' => $row['principaluri'],
330
-			'{DAV:}displayname' => $row['displayname'],
331
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
332
-			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
333
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
334
-		];
335
-
336
-		$this->addOwnerPrincipal($addressBook);
337
-
338
-		return $addressBook;
339
-	}
340
-
341
-	/**
342
-	 * Updates properties for an address book.
343
-	 *
344
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
345
-	 * To do the actual updates, you must tell this object which properties
346
-	 * you're going to process with the handle() method.
347
-	 *
348
-	 * Calling the handle method is like telling the PropPatch object "I
349
-	 * promise I can handle updating this property".
350
-	 *
351
-	 * Read the PropPatch documentation for more info and examples.
352
-	 *
353
-	 * @param string $addressBookId
354
-	 * @param \Sabre\DAV\PropPatch $propPatch
355
-	 * @return void
356
-	 */
357
-	function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
358
-		$supportedProperties = [
359
-			'{DAV:}displayname',
360
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
361
-		];
362
-
363
-		/**
364
-		 * @suppress SqlInjectionChecker
365
-		 */
366
-		$propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
367
-
368
-			$updates = [];
369
-			foreach($mutations as $property=>$newValue) {
370
-
371
-				switch($property) {
372
-					case '{DAV:}displayname':
373
-						$updates['displayname'] = $newValue;
374
-						break;
375
-					case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
376
-						$updates['description'] = $newValue;
377
-						break;
378
-				}
379
-			}
380
-			$query = $this->db->getQueryBuilder();
381
-			$query->update('addressbooks');
382
-
383
-			foreach($updates as $key=>$value) {
384
-				$query->set($key, $query->createNamedParameter($value));
385
-			}
386
-			$query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
387
-			->execute();
388
-
389
-			$this->addChange($addressBookId, "", 2);
390
-
391
-			return true;
392
-
393
-		});
394
-	}
395
-
396
-	/**
397
-	 * Creates a new address book
398
-	 *
399
-	 * @param string $principalUri
400
-	 * @param string $url Just the 'basename' of the url.
401
-	 * @param array $properties
402
-	 * @return int
403
-	 * @throws BadRequest
404
-	 */
405
-	function createAddressBook($principalUri, $url, array $properties) {
406
-		$values = [
407
-			'displayname' => null,
408
-			'description' => null,
409
-			'principaluri' => $principalUri,
410
-			'uri' => $url,
411
-			'synctoken' => 1
412
-		];
413
-
414
-		foreach($properties as $property=>$newValue) {
415
-
416
-			switch($property) {
417
-				case '{DAV:}displayname':
418
-					$values['displayname'] = $newValue;
419
-					break;
420
-				case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
421
-					$values['description'] = $newValue;
422
-					break;
423
-				default:
424
-					throw new BadRequest('Unknown property: ' . $property);
425
-			}
426
-
427
-		}
428
-
429
-		// Fallback to make sure the displayname is set. Some clients may refuse
430
-		// to work with addressbooks not having a displayname.
431
-		if(is_null($values['displayname'])) {
432
-			$values['displayname'] = $url;
433
-		}
434
-
435
-		$query = $this->db->getQueryBuilder();
436
-		$query->insert('addressbooks')
437
-			->values([
438
-				'uri' => $query->createParameter('uri'),
439
-				'displayname' => $query->createParameter('displayname'),
440
-				'description' => $query->createParameter('description'),
441
-				'principaluri' => $query->createParameter('principaluri'),
442
-				'synctoken' => $query->createParameter('synctoken'),
443
-			])
444
-			->setParameters($values)
445
-			->execute();
446
-
447
-		return $query->getLastInsertId();
448
-	}
449
-
450
-	/**
451
-	 * Deletes an entire addressbook and all its contents
452
-	 *
453
-	 * @param mixed $addressBookId
454
-	 * @return void
455
-	 */
456
-	function deleteAddressBook($addressBookId) {
457
-		$query = $this->db->getQueryBuilder();
458
-		$query->delete('cards')
459
-			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
460
-			->setParameter('addressbookid', $addressBookId)
461
-			->execute();
462
-
463
-		$query->delete('addressbookchanges')
464
-			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
465
-			->setParameter('addressbookid', $addressBookId)
466
-			->execute();
467
-
468
-		$query->delete('addressbooks')
469
-			->where($query->expr()->eq('id', $query->createParameter('id')))
470
-			->setParameter('id', $addressBookId)
471
-			->execute();
472
-
473
-		$this->sharingBackend->deleteAllShares($addressBookId);
474
-
475
-		$query->delete($this->dbCardsPropertiesTable)
476
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
477
-			->execute();
478
-
479
-	}
480
-
481
-	/**
482
-	 * Returns all cards for a specific addressbook id.
483
-	 *
484
-	 * This method should return the following properties for each card:
485
-	 *   * carddata - raw vcard data
486
-	 *   * uri - Some unique url
487
-	 *   * lastmodified - A unix timestamp
488
-	 *
489
-	 * It's recommended to also return the following properties:
490
-	 *   * etag - A unique etag. This must change every time the card changes.
491
-	 *   * size - The size of the card in bytes.
492
-	 *
493
-	 * If these last two properties are provided, less time will be spent
494
-	 * calculating them. If they are specified, you can also ommit carddata.
495
-	 * This may speed up certain requests, especially with large cards.
496
-	 *
497
-	 * @param mixed $addressBookId
498
-	 * @return array
499
-	 */
500
-	function getCards($addressBookId) {
501
-		$query = $this->db->getQueryBuilder();
502
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
503
-			->from('cards')
504
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
505
-
506
-		$cards = [];
507
-
508
-		$result = $query->execute();
509
-		while($row = $result->fetch()) {
510
-			$row['etag'] = '"' . $row['etag'] . '"';
511
-			$row['carddata'] = $this->readBlob($row['carddata']);
512
-			$cards[] = $row;
513
-		}
514
-		$result->closeCursor();
515
-
516
-		return $cards;
517
-	}
518
-
519
-	/**
520
-	 * Returns a specific card.
521
-	 *
522
-	 * The same set of properties must be returned as with getCards. The only
523
-	 * exception is that 'carddata' is absolutely required.
524
-	 *
525
-	 * If the card does not exist, you must return false.
526
-	 *
527
-	 * @param mixed $addressBookId
528
-	 * @param string $cardUri
529
-	 * @return array
530
-	 */
531
-	function getCard($addressBookId, $cardUri) {
532
-		$query = $this->db->getQueryBuilder();
533
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
534
-			->from('cards')
535
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
536
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
537
-			->setMaxResults(1);
538
-
539
-		$result = $query->execute();
540
-		$row = $result->fetch();
541
-		if (!$row) {
542
-			return false;
543
-		}
544
-		$row['etag'] = '"' . $row['etag'] . '"';
545
-		$row['carddata'] = $this->readBlob($row['carddata']);
546
-
547
-		return $row;
548
-	}
549
-
550
-	/**
551
-	 * Returns a list of cards.
552
-	 *
553
-	 * This method should work identical to getCard, but instead return all the
554
-	 * cards in the list as an array.
555
-	 *
556
-	 * If the backend supports this, it may allow for some speed-ups.
557
-	 *
558
-	 * @param mixed $addressBookId
559
-	 * @param string[] $uris
560
-	 * @return array
561
-	 */
562
-	function getMultipleCards($addressBookId, array $uris) {
563
-		if (empty($uris)) {
564
-			return [];
565
-		}
566
-
567
-		$chunks = array_chunk($uris, 100);
568
-		$cards = [];
569
-
570
-		$query = $this->db->getQueryBuilder();
571
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
572
-			->from('cards')
573
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
574
-			->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
575
-
576
-		foreach ($chunks as $uris) {
577
-			$query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
578
-			$result = $query->execute();
579
-
580
-			while ($row = $result->fetch()) {
581
-				$row['etag'] = '"' . $row['etag'] . '"';
582
-				$row['carddata'] = $this->readBlob($row['carddata']);
583
-				$cards[] = $row;
584
-			}
585
-			$result->closeCursor();
586
-		}
587
-		return $cards;
588
-	}
589
-
590
-	/**
591
-	 * Creates a new card.
592
-	 *
593
-	 * The addressbook id will be passed as the first argument. This is the
594
-	 * same id as it is returned from the getAddressBooksForUser method.
595
-	 *
596
-	 * The cardUri is a base uri, and doesn't include the full path. The
597
-	 * cardData argument is the vcard body, and is passed as a string.
598
-	 *
599
-	 * It is possible to return an ETag from this method. This ETag is for the
600
-	 * newly created resource, and must be enclosed with double quotes (that
601
-	 * is, the string itself must contain the double quotes).
602
-	 *
603
-	 * You should only return the ETag if you store the carddata as-is. If a
604
-	 * subsequent GET request on the same card does not have the same body,
605
-	 * byte-by-byte and you did return an ETag here, clients tend to get
606
-	 * confused.
607
-	 *
608
-	 * If you don't return an ETag, you can just return null.
609
-	 *
610
-	 * @param mixed $addressBookId
611
-	 * @param string $cardUri
612
-	 * @param string $cardData
613
-	 * @return string
614
-	 */
615
-	function createCard($addressBookId, $cardUri, $cardData) {
616
-		$etag = md5($cardData);
617
-		$uid = $this->getUID($cardData);
618
-
619
-		$q = $this->db->getQueryBuilder();
620
-		$q->select('uid')
621
-			->from('cards')
622
-			->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
623
-			->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
624
-			->setMaxResults(1);
625
-		$result = $q->execute();
626
-		$count = (bool) $result->fetchColumn();
627
-		$result->closeCursor();
628
-		if ($count) {
629
-			throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
630
-		}
631
-
632
-		$query = $this->db->getQueryBuilder();
633
-		$query->insert('cards')
634
-			->values([
635
-				'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
636
-				'uri' => $query->createNamedParameter($cardUri),
637
-				'lastmodified' => $query->createNamedParameter(time()),
638
-				'addressbookid' => $query->createNamedParameter($addressBookId),
639
-				'size' => $query->createNamedParameter(strlen($cardData)),
640
-				'etag' => $query->createNamedParameter($etag),
641
-				'uid' => $query->createNamedParameter($uid),
642
-			])
643
-			->execute();
644
-
645
-		$this->addChange($addressBookId, $cardUri, 1);
646
-		$this->updateProperties($addressBookId, $cardUri, $cardData);
647
-
648
-		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
649
-			new GenericEvent(null, [
650
-				'addressBookId' => $addressBookId,
651
-				'cardUri' => $cardUri,
652
-				'cardData' => $cardData]));
653
-
654
-		return '"' . $etag . '"';
655
-	}
656
-
657
-	/**
658
-	 * Updates a card.
659
-	 *
660
-	 * The addressbook id will be passed as the first argument. This is the
661
-	 * same id as it is returned from the getAddressBooksForUser method.
662
-	 *
663
-	 * The cardUri is a base uri, and doesn't include the full path. The
664
-	 * cardData argument is the vcard body, and is passed as a string.
665
-	 *
666
-	 * It is possible to return an ETag from this method. This ETag should
667
-	 * match that of the updated resource, and must be enclosed with double
668
-	 * quotes (that is: the string itself must contain the actual quotes).
669
-	 *
670
-	 * You should only return the ETag if you store the carddata as-is. If a
671
-	 * subsequent GET request on the same card does not have the same body,
672
-	 * byte-by-byte and you did return an ETag here, clients tend to get
673
-	 * confused.
674
-	 *
675
-	 * If you don't return an ETag, you can just return null.
676
-	 *
677
-	 * @param mixed $addressBookId
678
-	 * @param string $cardUri
679
-	 * @param string $cardData
680
-	 * @return string
681
-	 */
682
-	function updateCard($addressBookId, $cardUri, $cardData) {
683
-
684
-		$uid = $this->getUID($cardData);
685
-		$etag = md5($cardData);
686
-		$query = $this->db->getQueryBuilder();
687
-		$query->update('cards')
688
-			->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
689
-			->set('lastmodified', $query->createNamedParameter(time()))
690
-			->set('size', $query->createNamedParameter(strlen($cardData)))
691
-			->set('etag', $query->createNamedParameter($etag))
692
-			->set('uid', $query->createNamedParameter($uid))
693
-			->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
694
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
695
-			->execute();
696
-
697
-		$this->addChange($addressBookId, $cardUri, 2);
698
-		$this->updateProperties($addressBookId, $cardUri, $cardData);
699
-
700
-		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
701
-			new GenericEvent(null, [
702
-				'addressBookId' => $addressBookId,
703
-				'cardUri' => $cardUri,
704
-				'cardData' => $cardData]));
705
-
706
-		return '"' . $etag . '"';
707
-	}
708
-
709
-	/**
710
-	 * Deletes a card
711
-	 *
712
-	 * @param mixed $addressBookId
713
-	 * @param string $cardUri
714
-	 * @return bool
715
-	 */
716
-	function deleteCard($addressBookId, $cardUri) {
717
-		try {
718
-			$cardId = $this->getCardId($addressBookId, $cardUri);
719
-		} catch (\InvalidArgumentException $e) {
720
-			$cardId = null;
721
-		}
722
-		$query = $this->db->getQueryBuilder();
723
-		$ret = $query->delete('cards')
724
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
725
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
726
-			->execute();
727
-
728
-		$this->addChange($addressBookId, $cardUri, 3);
729
-
730
-		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
731
-			new GenericEvent(null, [
732
-				'addressBookId' => $addressBookId,
733
-				'cardUri' => $cardUri]));
734
-
735
-		if ($ret === 1) {
736
-			if ($cardId !== null) {
737
-				$this->purgeProperties($addressBookId, $cardId);
738
-			}
739
-			return true;
740
-		}
741
-
742
-		return false;
743
-	}
744
-
745
-	/**
746
-	 * The getChanges method returns all the changes that have happened, since
747
-	 * the specified syncToken in the specified address book.
748
-	 *
749
-	 * This function should return an array, such as the following:
750
-	 *
751
-	 * [
752
-	 *   'syncToken' => 'The current synctoken',
753
-	 *   'added'   => [
754
-	 *      'new.txt',
755
-	 *   ],
756
-	 *   'modified'   => [
757
-	 *      'modified.txt',
758
-	 *   ],
759
-	 *   'deleted' => [
760
-	 *      'foo.php.bak',
761
-	 *      'old.txt'
762
-	 *   ]
763
-	 * ];
764
-	 *
765
-	 * The returned syncToken property should reflect the *current* syncToken
766
-	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
767
-	 * property. This is needed here too, to ensure the operation is atomic.
768
-	 *
769
-	 * If the $syncToken argument is specified as null, this is an initial
770
-	 * sync, and all members should be reported.
771
-	 *
772
-	 * The modified property is an array of nodenames that have changed since
773
-	 * the last token.
774
-	 *
775
-	 * The deleted property is an array with nodenames, that have been deleted
776
-	 * from collection.
777
-	 *
778
-	 * The $syncLevel argument is basically the 'depth' of the report. If it's
779
-	 * 1, you only have to report changes that happened only directly in
780
-	 * immediate descendants. If it's 2, it should also include changes from
781
-	 * the nodes below the child collections. (grandchildren)
782
-	 *
783
-	 * The $limit argument allows a client to specify how many results should
784
-	 * be returned at most. If the limit is not specified, it should be treated
785
-	 * as infinite.
786
-	 *
787
-	 * If the limit (infinite or not) is higher than you're willing to return,
788
-	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
789
-	 *
790
-	 * If the syncToken is expired (due to data cleanup) or unknown, you must
791
-	 * return null.
792
-	 *
793
-	 * The limit is 'suggestive'. You are free to ignore it.
794
-	 *
795
-	 * @param string $addressBookId
796
-	 * @param string $syncToken
797
-	 * @param int $syncLevel
798
-	 * @param int $limit
799
-	 * @return array
800
-	 */
801
-	function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
802
-		// Current synctoken
803
-		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
804
-		$stmt->execute([ $addressBookId ]);
805
-		$currentToken = $stmt->fetchColumn(0);
806
-
807
-		if (is_null($currentToken)) return null;
808
-
809
-		$result = [
810
-			'syncToken' => $currentToken,
811
-			'added'     => [],
812
-			'modified'  => [],
813
-			'deleted'   => [],
814
-		];
815
-
816
-		if ($syncToken) {
817
-
818
-			$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
819
-			if ($limit>0) {
820
-				$query .= " LIMIT " . (int)$limit;
821
-			}
822
-
823
-			// Fetching all changes
824
-			$stmt = $this->db->prepare($query);
825
-			$stmt->execute([$syncToken, $currentToken, $addressBookId]);
826
-
827
-			$changes = [];
828
-
829
-			// This loop ensures that any duplicates are overwritten, only the
830
-			// last change on a node is relevant.
831
-			while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
832
-
833
-				$changes[$row['uri']] = $row['operation'];
834
-
835
-			}
836
-
837
-			foreach($changes as $uri => $operation) {
838
-
839
-				switch($operation) {
840
-					case 1:
841
-						$result['added'][] = $uri;
842
-						break;
843
-					case 2:
844
-						$result['modified'][] = $uri;
845
-						break;
846
-					case 3:
847
-						$result['deleted'][] = $uri;
848
-						break;
849
-				}
850
-
851
-			}
852
-		} else {
853
-			// No synctoken supplied, this is the initial sync.
854
-			$query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
855
-			$stmt = $this->db->prepare($query);
856
-			$stmt->execute([$addressBookId]);
857
-
858
-			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
859
-		}
860
-		return $result;
861
-	}
862
-
863
-	/**
864
-	 * Adds a change record to the addressbookchanges table.
865
-	 *
866
-	 * @param mixed $addressBookId
867
-	 * @param string $objectUri
868
-	 * @param int $operation 1 = add, 2 = modify, 3 = delete
869
-	 * @return void
870
-	 */
871
-	protected function addChange($addressBookId, $objectUri, $operation) {
872
-		$sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
873
-		$stmt = $this->db->prepare($sql);
874
-		$stmt->execute([
875
-			$objectUri,
876
-			$addressBookId,
877
-			$operation,
878
-			$addressBookId
879
-		]);
880
-		$stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
881
-		$stmt->execute([
882
-			$addressBookId
883
-		]);
884
-	}
885
-
886
-	private function readBlob($cardData) {
887
-		if (is_resource($cardData)) {
888
-			return stream_get_contents($cardData);
889
-		}
890
-
891
-		return $cardData;
892
-	}
893
-
894
-	/**
895
-	 * @param IShareable $shareable
896
-	 * @param string[] $add
897
-	 * @param string[] $remove
898
-	 */
899
-	public function updateShares(IShareable $shareable, $add, $remove) {
900
-		$this->sharingBackend->updateShares($shareable, $add, $remove);
901
-	}
902
-
903
-	/**
904
-	 * search contact
905
-	 *
906
-	 * @param int $addressBookId
907
-	 * @param string $pattern which should match within the $searchProperties
908
-	 * @param array $searchProperties defines the properties within the query pattern should match
909
-	 * @param array $options = array() to define the search behavior
910
-	 * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are
911
-	 * @return array an array of contacts which are arrays of key-value-pairs
912
-	 */
913
-	public function search($addressBookId, $pattern, $searchProperties, $options = []) {
914
-		$query = $this->db->getQueryBuilder();
915
-		$query2 = $this->db->getQueryBuilder();
916
-
917
-		$query2->selectDistinct('cp.cardid')->from($this->dbCardsPropertiesTable, 'cp');
918
-		$query2->andWhere($query2->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId)));
919
-		$or = $query2->expr()->orX();
920
-		foreach ($searchProperties as $property) {
921
-			$or->add($query2->expr()->eq('cp.name', $query->createNamedParameter($property)));
922
-		}
923
-		$query2->andWhere($or);
924
-
925
-		// No need for like when the pattern is empty
926
-		if ('' !== $pattern) {
927
-			if(\array_key_exists('escape_like_param', $options) && $options['escape_like_param'] === false) {
928
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter($pattern)));
929
-			} else {
930
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
931
-			}
932
-		}
933
-
934
-		$query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c')
935
-			->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL())));
936
-
937
-		$result = $query->execute();
938
-		$cards = $result->fetchAll();
939
-
940
-		$result->closeCursor();
941
-
942
-		return array_map(function ($array) {
943
-			$array['carddata'] = $this->readBlob($array['carddata']);
944
-			return $array;
945
-		}, $cards);
946
-	}
947
-
948
-	/**
949
-	 * @param int $bookId
950
-	 * @param string $name
951
-	 * @return array
952
-	 */
953
-	public function collectCardProperties($bookId, $name) {
954
-		$query = $this->db->getQueryBuilder();
955
-		$result = $query->selectDistinct('value')
956
-			->from($this->dbCardsPropertiesTable)
957
-			->where($query->expr()->eq('name', $query->createNamedParameter($name)))
958
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
959
-			->execute();
960
-
961
-		$all = $result->fetchAll(PDO::FETCH_COLUMN);
962
-		$result->closeCursor();
963
-
964
-		return $all;
965
-	}
966
-
967
-	/**
968
-	 * get URI from a given contact
969
-	 *
970
-	 * @param int $id
971
-	 * @return string
972
-	 */
973
-	public function getCardUri($id) {
974
-		$query = $this->db->getQueryBuilder();
975
-		$query->select('uri')->from($this->dbCardsTable)
976
-				->where($query->expr()->eq('id', $query->createParameter('id')))
977
-				->setParameter('id', $id);
978
-
979
-		$result = $query->execute();
980
-		$uri = $result->fetch();
981
-		$result->closeCursor();
982
-
983
-		if (!isset($uri['uri'])) {
984
-			throw new \InvalidArgumentException('Card does not exists: ' . $id);
985
-		}
986
-
987
-		return $uri['uri'];
988
-	}
989
-
990
-	/**
991
-	 * return contact with the given URI
992
-	 *
993
-	 * @param int $addressBookId
994
-	 * @param string $uri
995
-	 * @returns array
996
-	 */
997
-	public function getContact($addressBookId, $uri) {
998
-		$result = [];
999
-		$query = $this->db->getQueryBuilder();
1000
-		$query->select('*')->from($this->dbCardsTable)
1001
-				->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1002
-				->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1003
-		$queryResult = $query->execute();
1004
-		$contact = $queryResult->fetch();
1005
-		$queryResult->closeCursor();
1006
-
1007
-		if (is_array($contact)) {
1008
-			$result = $contact;
1009
-		}
1010
-
1011
-		return $result;
1012
-	}
1013
-
1014
-	/**
1015
-	 * Returns the list of people whom this address book is shared with.
1016
-	 *
1017
-	 * Every element in this array should have the following properties:
1018
-	 *   * href - Often a mailto: address
1019
-	 *   * commonName - Optional, for example a first + last name
1020
-	 *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
1021
-	 *   * readOnly - boolean
1022
-	 *   * summary - Optional, a description for the share
1023
-	 *
1024
-	 * @return array
1025
-	 */
1026
-	public function getShares($addressBookId) {
1027
-		return $this->sharingBackend->getShares($addressBookId);
1028
-	}
1029
-
1030
-	/**
1031
-	 * update properties table
1032
-	 *
1033
-	 * @param int $addressBookId
1034
-	 * @param string $cardUri
1035
-	 * @param string $vCardSerialized
1036
-	 */
1037
-	protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1038
-		$cardId = $this->getCardId($addressBookId, $cardUri);
1039
-		$vCard = $this->readCard($vCardSerialized);
1040
-
1041
-		$this->purgeProperties($addressBookId, $cardId);
1042
-
1043
-		$query = $this->db->getQueryBuilder();
1044
-		$query->insert($this->dbCardsPropertiesTable)
1045
-			->values(
1046
-				[
1047
-					'addressbookid' => $query->createNamedParameter($addressBookId),
1048
-					'cardid' => $query->createNamedParameter($cardId),
1049
-					'name' => $query->createParameter('name'),
1050
-					'value' => $query->createParameter('value'),
1051
-					'preferred' => $query->createParameter('preferred')
1052
-				]
1053
-			);
1054
-
1055
-		foreach ($vCard->children() as $property) {
1056
-			if(!in_array($property->name, self::$indexProperties)) {
1057
-				continue;
1058
-			}
1059
-			$preferred = 0;
1060
-			foreach($property->parameters as $parameter) {
1061
-				if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1062
-					$preferred = 1;
1063
-					break;
1064
-				}
1065
-			}
1066
-			$query->setParameter('name', $property->name);
1067
-			$query->setParameter('value', mb_substr($property->getValue(), 0, 254));
1068
-			$query->setParameter('preferred', $preferred);
1069
-			$query->execute();
1070
-		}
1071
-	}
1072
-
1073
-	/**
1074
-	 * read vCard data into a vCard object
1075
-	 *
1076
-	 * @param string $cardData
1077
-	 * @return VCard
1078
-	 */
1079
-	protected function readCard($cardData) {
1080
-		return  Reader::read($cardData);
1081
-	}
1082
-
1083
-	/**
1084
-	 * delete all properties from a given card
1085
-	 *
1086
-	 * @param int $addressBookId
1087
-	 * @param int $cardId
1088
-	 */
1089
-	protected function purgeProperties($addressBookId, $cardId) {
1090
-		$query = $this->db->getQueryBuilder();
1091
-		$query->delete($this->dbCardsPropertiesTable)
1092
-			->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1093
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1094
-		$query->execute();
1095
-	}
1096
-
1097
-	/**
1098
-	 * get ID from a given contact
1099
-	 *
1100
-	 * @param int $addressBookId
1101
-	 * @param string $uri
1102
-	 * @return int
1103
-	 */
1104
-	protected function getCardId($addressBookId, $uri) {
1105
-		$query = $this->db->getQueryBuilder();
1106
-		$query->select('id')->from($this->dbCardsTable)
1107
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1108
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1109
-
1110
-		$result = $query->execute();
1111
-		$cardIds = $result->fetch();
1112
-		$result->closeCursor();
1113
-
1114
-		if (!isset($cardIds['id'])) {
1115
-			throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1116
-		}
1117
-
1118
-		return (int)$cardIds['id'];
1119
-	}
1120
-
1121
-	/**
1122
-	 * For shared address books the sharee is set in the ACL of the address book
1123
-	 * @param $addressBookId
1124
-	 * @param $acl
1125
-	 * @return array
1126
-	 */
1127
-	public function applyShareAcl($addressBookId, $acl) {
1128
-		return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1129
-	}
1130
-
1131
-	private function convertPrincipal($principalUri, $toV2) {
1132
-		if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1133
-			list(, $name) = \Sabre\Uri\split($principalUri);
1134
-			if ($toV2 === true) {
1135
-				return "principals/users/$name";
1136
-			}
1137
-			return "principals/$name";
1138
-		}
1139
-		return $principalUri;
1140
-	}
1141
-
1142
-	private function addOwnerPrincipal(&$addressbookInfo) {
1143
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1144
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1145
-		if (isset($addressbookInfo[$ownerPrincipalKey])) {
1146
-			$uri = $addressbookInfo[$ownerPrincipalKey];
1147
-		} else {
1148
-			$uri = $addressbookInfo['principaluri'];
1149
-		}
1150
-
1151
-		$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1152
-		if (isset($principalInformation['{DAV:}displayname'])) {
1153
-			$addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1154
-		}
1155
-	}
1156
-
1157
-	/**
1158
-	 * Extract UID from vcard
1159
-	 *
1160
-	 * @param string $cardData the vcard raw data
1161
-	 * @return string the uid
1162
-	 * @throws BadRequest if no UID is available
1163
-	 */
1164
-	private function getUID($cardData) {
1165
-		if ($cardData != '') {
1166
-			$vCard = Reader::read($cardData);
1167
-			if ($vCard->UID) {
1168
-				$uid = $vCard->UID->getValue();
1169
-				return $uid;
1170
-			}
1171
-			// should already be handled, but just in case
1172
-			throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
1173
-		}
1174
-		// should already be handled, but just in case
1175
-		throw new BadRequest('vCard can not be empty');
1176
-	}
59
+    const PERSONAL_ADDRESSBOOK_URI = 'contacts';
60
+    const PERSONAL_ADDRESSBOOK_NAME = 'Contacts';
61
+
62
+    /** @var Principal */
63
+    private $principalBackend;
64
+
65
+    /** @var string */
66
+    private $dbCardsTable = 'cards';
67
+
68
+    /** @var string */
69
+    private $dbCardsPropertiesTable = 'cards_properties';
70
+
71
+    /** @var IDBConnection */
72
+    private $db;
73
+
74
+    /** @var Backend */
75
+    private $sharingBackend;
76
+
77
+    /** @var array properties to index */
78
+    public static $indexProperties = [
79
+        'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
80
+        'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD'];
81
+
82
+    /**
83
+     * @var string[] Map of uid => display name
84
+     */
85
+    protected $userDisplayNames;
86
+
87
+    /** @var IUserManager */
88
+    private $userManager;
89
+
90
+    /** @var EventDispatcherInterface */
91
+    private $dispatcher;
92
+
93
+    /**
94
+     * CardDavBackend constructor.
95
+     *
96
+     * @param IDBConnection $db
97
+     * @param Principal $principalBackend
98
+     * @param IUserManager $userManager
99
+     * @param IGroupManager $groupManager
100
+     * @param EventDispatcherInterface $dispatcher
101
+     */
102
+    public function __construct(IDBConnection $db,
103
+                                Principal $principalBackend,
104
+                                IUserManager $userManager,
105
+                                IGroupManager $groupManager,
106
+                                EventDispatcherInterface $dispatcher) {
107
+        $this->db = $db;
108
+        $this->principalBackend = $principalBackend;
109
+        $this->userManager = $userManager;
110
+        $this->dispatcher = $dispatcher;
111
+        $this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'addressbook');
112
+    }
113
+
114
+    /**
115
+     * Return the number of address books for a principal
116
+     *
117
+     * @param $principalUri
118
+     * @return int
119
+     */
120
+    public function getAddressBooksForUserCount($principalUri) {
121
+        $principalUri = $this->convertPrincipal($principalUri, true);
122
+        $query = $this->db->getQueryBuilder();
123
+        $query->select($query->func()->count('*'))
124
+            ->from('addressbooks')
125
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
126
+
127
+        return (int)$query->execute()->fetchColumn();
128
+    }
129
+
130
+    /**
131
+     * Returns the list of address books for a specific user.
132
+     *
133
+     * Every addressbook should have the following properties:
134
+     *   id - an arbitrary unique id
135
+     *   uri - the 'basename' part of the url
136
+     *   principaluri - Same as the passed parameter
137
+     *
138
+     * Any additional clark-notation property may be passed besides this. Some
139
+     * common ones are :
140
+     *   {DAV:}displayname
141
+     *   {urn:ietf:params:xml:ns:carddav}addressbook-description
142
+     *   {http://calendarserver.org/ns/}getctag
143
+     *
144
+     * @param string $principalUri
145
+     * @return array
146
+     */
147
+    function getAddressBooksForUser($principalUri) {
148
+        $principalUriOriginal = $principalUri;
149
+        $principalUri = $this->convertPrincipal($principalUri, true);
150
+        $query = $this->db->getQueryBuilder();
151
+        $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
152
+            ->from('addressbooks')
153
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
154
+
155
+        $addressBooks = [];
156
+
157
+        $result = $query->execute();
158
+        while($row = $result->fetch()) {
159
+            $addressBooks[$row['id']] = [
160
+                'id'  => $row['id'],
161
+                'uri' => $row['uri'],
162
+                'principaluri' => $this->convertPrincipal($row['principaluri'], false),
163
+                '{DAV:}displayname' => $row['displayname'],
164
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
165
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
166
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
167
+            ];
168
+
169
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
170
+        }
171
+        $result->closeCursor();
172
+
173
+        // query for shared addressbooks
174
+        $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
175
+        $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
176
+
177
+        $principals = array_map(function ($principal) {
178
+            return urldecode($principal);
179
+        }, $principals);
180
+        $principals[]= $principalUri;
181
+
182
+        $query = $this->db->getQueryBuilder();
183
+        $result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
184
+            ->from('dav_shares', 's')
185
+            ->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
186
+            ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
187
+            ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
188
+            ->setParameter('type', 'addressbook')
189
+            ->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
190
+            ->execute();
191
+
192
+        $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
193
+        while($row = $result->fetch()) {
194
+            if ($row['principaluri'] === $principalUri) {
195
+                continue;
196
+            }
197
+
198
+            $readOnly = (int) $row['access'] === Backend::ACCESS_READ;
199
+            if (isset($addressBooks[$row['id']])) {
200
+                if ($readOnly) {
201
+                    // New share can not have more permissions then the old one.
202
+                    continue;
203
+                }
204
+                if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
205
+                    $addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
206
+                    // Old share is already read-write, no more permissions can be gained
207
+                    continue;
208
+                }
209
+            }
210
+
211
+            list(, $name) = \Sabre\Uri\split($row['principaluri']);
212
+            $uri = $row['uri'] . '_shared_by_' . $name;
213
+            $displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
214
+
215
+            $addressBooks[$row['id']] = [
216
+                'id'  => $row['id'],
217
+                'uri' => $uri,
218
+                'principaluri' => $principalUriOriginal,
219
+                '{DAV:}displayname' => $displayName,
220
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
221
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
222
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
223
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
224
+                $readOnlyPropertyName => $readOnly,
225
+            ];
226
+
227
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
228
+        }
229
+        $result->closeCursor();
230
+
231
+        return array_values($addressBooks);
232
+    }
233
+
234
+    public function getUsersOwnAddressBooks($principalUri) {
235
+        $principalUri = $this->convertPrincipal($principalUri, true);
236
+        $query = $this->db->getQueryBuilder();
237
+        $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
238
+                ->from('addressbooks')
239
+                ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
240
+
241
+        $addressBooks = [];
242
+
243
+        $result = $query->execute();
244
+        while($row = $result->fetch()) {
245
+            $addressBooks[$row['id']] = [
246
+                'id'  => $row['id'],
247
+                'uri' => $row['uri'],
248
+                'principaluri' => $this->convertPrincipal($row['principaluri'], false),
249
+                '{DAV:}displayname' => $row['displayname'],
250
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
251
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
252
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
253
+            ];
254
+
255
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
256
+        }
257
+        $result->closeCursor();
258
+
259
+        return array_values($addressBooks);
260
+    }
261
+
262
+    private function getUserDisplayName($uid) {
263
+        if (!isset($this->userDisplayNames[$uid])) {
264
+            $user = $this->userManager->get($uid);
265
+
266
+            if ($user instanceof IUser) {
267
+                $this->userDisplayNames[$uid] = $user->getDisplayName();
268
+            } else {
269
+                $this->userDisplayNames[$uid] = $uid;
270
+            }
271
+        }
272
+
273
+        return $this->userDisplayNames[$uid];
274
+    }
275
+
276
+    /**
277
+     * @param int $addressBookId
278
+     */
279
+    public function getAddressBookById($addressBookId) {
280
+        $query = $this->db->getQueryBuilder();
281
+        $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
282
+            ->from('addressbooks')
283
+            ->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
284
+            ->execute();
285
+
286
+        $row = $result->fetch();
287
+        $result->closeCursor();
288
+        if ($row === false) {
289
+            return null;
290
+        }
291
+
292
+        $addressBook = [
293
+            'id'  => $row['id'],
294
+            'uri' => $row['uri'],
295
+            'principaluri' => $row['principaluri'],
296
+            '{DAV:}displayname' => $row['displayname'],
297
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
298
+            '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
299
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
300
+        ];
301
+
302
+        $this->addOwnerPrincipal($addressBook);
303
+
304
+        return $addressBook;
305
+    }
306
+
307
+    /**
308
+     * @param $addressBookUri
309
+     * @return array|null
310
+     */
311
+    public function getAddressBooksByUri($principal, $addressBookUri) {
312
+        $query = $this->db->getQueryBuilder();
313
+        $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
314
+            ->from('addressbooks')
315
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
316
+            ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
317
+            ->setMaxResults(1)
318
+            ->execute();
319
+
320
+        $row = $result->fetch();
321
+        $result->closeCursor();
322
+        if ($row === false) {
323
+            return null;
324
+        }
325
+
326
+        $addressBook = [
327
+            'id'  => $row['id'],
328
+            'uri' => $row['uri'],
329
+            'principaluri' => $row['principaluri'],
330
+            '{DAV:}displayname' => $row['displayname'],
331
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
332
+            '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
333
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
334
+        ];
335
+
336
+        $this->addOwnerPrincipal($addressBook);
337
+
338
+        return $addressBook;
339
+    }
340
+
341
+    /**
342
+     * Updates properties for an address book.
343
+     *
344
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
345
+     * To do the actual updates, you must tell this object which properties
346
+     * you're going to process with the handle() method.
347
+     *
348
+     * Calling the handle method is like telling the PropPatch object "I
349
+     * promise I can handle updating this property".
350
+     *
351
+     * Read the PropPatch documentation for more info and examples.
352
+     *
353
+     * @param string $addressBookId
354
+     * @param \Sabre\DAV\PropPatch $propPatch
355
+     * @return void
356
+     */
357
+    function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
358
+        $supportedProperties = [
359
+            '{DAV:}displayname',
360
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description',
361
+        ];
362
+
363
+        /**
364
+         * @suppress SqlInjectionChecker
365
+         */
366
+        $propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
367
+
368
+            $updates = [];
369
+            foreach($mutations as $property=>$newValue) {
370
+
371
+                switch($property) {
372
+                    case '{DAV:}displayname':
373
+                        $updates['displayname'] = $newValue;
374
+                        break;
375
+                    case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
376
+                        $updates['description'] = $newValue;
377
+                        break;
378
+                }
379
+            }
380
+            $query = $this->db->getQueryBuilder();
381
+            $query->update('addressbooks');
382
+
383
+            foreach($updates as $key=>$value) {
384
+                $query->set($key, $query->createNamedParameter($value));
385
+            }
386
+            $query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
387
+            ->execute();
388
+
389
+            $this->addChange($addressBookId, "", 2);
390
+
391
+            return true;
392
+
393
+        });
394
+    }
395
+
396
+    /**
397
+     * Creates a new address book
398
+     *
399
+     * @param string $principalUri
400
+     * @param string $url Just the 'basename' of the url.
401
+     * @param array $properties
402
+     * @return int
403
+     * @throws BadRequest
404
+     */
405
+    function createAddressBook($principalUri, $url, array $properties) {
406
+        $values = [
407
+            'displayname' => null,
408
+            'description' => null,
409
+            'principaluri' => $principalUri,
410
+            'uri' => $url,
411
+            'synctoken' => 1
412
+        ];
413
+
414
+        foreach($properties as $property=>$newValue) {
415
+
416
+            switch($property) {
417
+                case '{DAV:}displayname':
418
+                    $values['displayname'] = $newValue;
419
+                    break;
420
+                case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
421
+                    $values['description'] = $newValue;
422
+                    break;
423
+                default:
424
+                    throw new BadRequest('Unknown property: ' . $property);
425
+            }
426
+
427
+        }
428
+
429
+        // Fallback to make sure the displayname is set. Some clients may refuse
430
+        // to work with addressbooks not having a displayname.
431
+        if(is_null($values['displayname'])) {
432
+            $values['displayname'] = $url;
433
+        }
434
+
435
+        $query = $this->db->getQueryBuilder();
436
+        $query->insert('addressbooks')
437
+            ->values([
438
+                'uri' => $query->createParameter('uri'),
439
+                'displayname' => $query->createParameter('displayname'),
440
+                'description' => $query->createParameter('description'),
441
+                'principaluri' => $query->createParameter('principaluri'),
442
+                'synctoken' => $query->createParameter('synctoken'),
443
+            ])
444
+            ->setParameters($values)
445
+            ->execute();
446
+
447
+        return $query->getLastInsertId();
448
+    }
449
+
450
+    /**
451
+     * Deletes an entire addressbook and all its contents
452
+     *
453
+     * @param mixed $addressBookId
454
+     * @return void
455
+     */
456
+    function deleteAddressBook($addressBookId) {
457
+        $query = $this->db->getQueryBuilder();
458
+        $query->delete('cards')
459
+            ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
460
+            ->setParameter('addressbookid', $addressBookId)
461
+            ->execute();
462
+
463
+        $query->delete('addressbookchanges')
464
+            ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
465
+            ->setParameter('addressbookid', $addressBookId)
466
+            ->execute();
467
+
468
+        $query->delete('addressbooks')
469
+            ->where($query->expr()->eq('id', $query->createParameter('id')))
470
+            ->setParameter('id', $addressBookId)
471
+            ->execute();
472
+
473
+        $this->sharingBackend->deleteAllShares($addressBookId);
474
+
475
+        $query->delete($this->dbCardsPropertiesTable)
476
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
477
+            ->execute();
478
+
479
+    }
480
+
481
+    /**
482
+     * Returns all cards for a specific addressbook id.
483
+     *
484
+     * This method should return the following properties for each card:
485
+     *   * carddata - raw vcard data
486
+     *   * uri - Some unique url
487
+     *   * lastmodified - A unix timestamp
488
+     *
489
+     * It's recommended to also return the following properties:
490
+     *   * etag - A unique etag. This must change every time the card changes.
491
+     *   * size - The size of the card in bytes.
492
+     *
493
+     * If these last two properties are provided, less time will be spent
494
+     * calculating them. If they are specified, you can also ommit carddata.
495
+     * This may speed up certain requests, especially with large cards.
496
+     *
497
+     * @param mixed $addressBookId
498
+     * @return array
499
+     */
500
+    function getCards($addressBookId) {
501
+        $query = $this->db->getQueryBuilder();
502
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
503
+            ->from('cards')
504
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
505
+
506
+        $cards = [];
507
+
508
+        $result = $query->execute();
509
+        while($row = $result->fetch()) {
510
+            $row['etag'] = '"' . $row['etag'] . '"';
511
+            $row['carddata'] = $this->readBlob($row['carddata']);
512
+            $cards[] = $row;
513
+        }
514
+        $result->closeCursor();
515
+
516
+        return $cards;
517
+    }
518
+
519
+    /**
520
+     * Returns a specific card.
521
+     *
522
+     * The same set of properties must be returned as with getCards. The only
523
+     * exception is that 'carddata' is absolutely required.
524
+     *
525
+     * If the card does not exist, you must return false.
526
+     *
527
+     * @param mixed $addressBookId
528
+     * @param string $cardUri
529
+     * @return array
530
+     */
531
+    function getCard($addressBookId, $cardUri) {
532
+        $query = $this->db->getQueryBuilder();
533
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
534
+            ->from('cards')
535
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
536
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
537
+            ->setMaxResults(1);
538
+
539
+        $result = $query->execute();
540
+        $row = $result->fetch();
541
+        if (!$row) {
542
+            return false;
543
+        }
544
+        $row['etag'] = '"' . $row['etag'] . '"';
545
+        $row['carddata'] = $this->readBlob($row['carddata']);
546
+
547
+        return $row;
548
+    }
549
+
550
+    /**
551
+     * Returns a list of cards.
552
+     *
553
+     * This method should work identical to getCard, but instead return all the
554
+     * cards in the list as an array.
555
+     *
556
+     * If the backend supports this, it may allow for some speed-ups.
557
+     *
558
+     * @param mixed $addressBookId
559
+     * @param string[] $uris
560
+     * @return array
561
+     */
562
+    function getMultipleCards($addressBookId, array $uris) {
563
+        if (empty($uris)) {
564
+            return [];
565
+        }
566
+
567
+        $chunks = array_chunk($uris, 100);
568
+        $cards = [];
569
+
570
+        $query = $this->db->getQueryBuilder();
571
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
572
+            ->from('cards')
573
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
574
+            ->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
575
+
576
+        foreach ($chunks as $uris) {
577
+            $query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
578
+            $result = $query->execute();
579
+
580
+            while ($row = $result->fetch()) {
581
+                $row['etag'] = '"' . $row['etag'] . '"';
582
+                $row['carddata'] = $this->readBlob($row['carddata']);
583
+                $cards[] = $row;
584
+            }
585
+            $result->closeCursor();
586
+        }
587
+        return $cards;
588
+    }
589
+
590
+    /**
591
+     * Creates a new card.
592
+     *
593
+     * The addressbook id will be passed as the first argument. This is the
594
+     * same id as it is returned from the getAddressBooksForUser method.
595
+     *
596
+     * The cardUri is a base uri, and doesn't include the full path. The
597
+     * cardData argument is the vcard body, and is passed as a string.
598
+     *
599
+     * It is possible to return an ETag from this method. This ETag is for the
600
+     * newly created resource, and must be enclosed with double quotes (that
601
+     * is, the string itself must contain the double quotes).
602
+     *
603
+     * You should only return the ETag if you store the carddata as-is. If a
604
+     * subsequent GET request on the same card does not have the same body,
605
+     * byte-by-byte and you did return an ETag here, clients tend to get
606
+     * confused.
607
+     *
608
+     * If you don't return an ETag, you can just return null.
609
+     *
610
+     * @param mixed $addressBookId
611
+     * @param string $cardUri
612
+     * @param string $cardData
613
+     * @return string
614
+     */
615
+    function createCard($addressBookId, $cardUri, $cardData) {
616
+        $etag = md5($cardData);
617
+        $uid = $this->getUID($cardData);
618
+
619
+        $q = $this->db->getQueryBuilder();
620
+        $q->select('uid')
621
+            ->from('cards')
622
+            ->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
623
+            ->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
624
+            ->setMaxResults(1);
625
+        $result = $q->execute();
626
+        $count = (bool) $result->fetchColumn();
627
+        $result->closeCursor();
628
+        if ($count) {
629
+            throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
630
+        }
631
+
632
+        $query = $this->db->getQueryBuilder();
633
+        $query->insert('cards')
634
+            ->values([
635
+                'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
636
+                'uri' => $query->createNamedParameter($cardUri),
637
+                'lastmodified' => $query->createNamedParameter(time()),
638
+                'addressbookid' => $query->createNamedParameter($addressBookId),
639
+                'size' => $query->createNamedParameter(strlen($cardData)),
640
+                'etag' => $query->createNamedParameter($etag),
641
+                'uid' => $query->createNamedParameter($uid),
642
+            ])
643
+            ->execute();
644
+
645
+        $this->addChange($addressBookId, $cardUri, 1);
646
+        $this->updateProperties($addressBookId, $cardUri, $cardData);
647
+
648
+        $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
649
+            new GenericEvent(null, [
650
+                'addressBookId' => $addressBookId,
651
+                'cardUri' => $cardUri,
652
+                'cardData' => $cardData]));
653
+
654
+        return '"' . $etag . '"';
655
+    }
656
+
657
+    /**
658
+     * Updates a card.
659
+     *
660
+     * The addressbook id will be passed as the first argument. This is the
661
+     * same id as it is returned from the getAddressBooksForUser method.
662
+     *
663
+     * The cardUri is a base uri, and doesn't include the full path. The
664
+     * cardData argument is the vcard body, and is passed as a string.
665
+     *
666
+     * It is possible to return an ETag from this method. This ETag should
667
+     * match that of the updated resource, and must be enclosed with double
668
+     * quotes (that is: the string itself must contain the actual quotes).
669
+     *
670
+     * You should only return the ETag if you store the carddata as-is. If a
671
+     * subsequent GET request on the same card does not have the same body,
672
+     * byte-by-byte and you did return an ETag here, clients tend to get
673
+     * confused.
674
+     *
675
+     * If you don't return an ETag, you can just return null.
676
+     *
677
+     * @param mixed $addressBookId
678
+     * @param string $cardUri
679
+     * @param string $cardData
680
+     * @return string
681
+     */
682
+    function updateCard($addressBookId, $cardUri, $cardData) {
683
+
684
+        $uid = $this->getUID($cardData);
685
+        $etag = md5($cardData);
686
+        $query = $this->db->getQueryBuilder();
687
+        $query->update('cards')
688
+            ->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
689
+            ->set('lastmodified', $query->createNamedParameter(time()))
690
+            ->set('size', $query->createNamedParameter(strlen($cardData)))
691
+            ->set('etag', $query->createNamedParameter($etag))
692
+            ->set('uid', $query->createNamedParameter($uid))
693
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
694
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
695
+            ->execute();
696
+
697
+        $this->addChange($addressBookId, $cardUri, 2);
698
+        $this->updateProperties($addressBookId, $cardUri, $cardData);
699
+
700
+        $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
701
+            new GenericEvent(null, [
702
+                'addressBookId' => $addressBookId,
703
+                'cardUri' => $cardUri,
704
+                'cardData' => $cardData]));
705
+
706
+        return '"' . $etag . '"';
707
+    }
708
+
709
+    /**
710
+     * Deletes a card
711
+     *
712
+     * @param mixed $addressBookId
713
+     * @param string $cardUri
714
+     * @return bool
715
+     */
716
+    function deleteCard($addressBookId, $cardUri) {
717
+        try {
718
+            $cardId = $this->getCardId($addressBookId, $cardUri);
719
+        } catch (\InvalidArgumentException $e) {
720
+            $cardId = null;
721
+        }
722
+        $query = $this->db->getQueryBuilder();
723
+        $ret = $query->delete('cards')
724
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
725
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
726
+            ->execute();
727
+
728
+        $this->addChange($addressBookId, $cardUri, 3);
729
+
730
+        $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
731
+            new GenericEvent(null, [
732
+                'addressBookId' => $addressBookId,
733
+                'cardUri' => $cardUri]));
734
+
735
+        if ($ret === 1) {
736
+            if ($cardId !== null) {
737
+                $this->purgeProperties($addressBookId, $cardId);
738
+            }
739
+            return true;
740
+        }
741
+
742
+        return false;
743
+    }
744
+
745
+    /**
746
+     * The getChanges method returns all the changes that have happened, since
747
+     * the specified syncToken in the specified address book.
748
+     *
749
+     * This function should return an array, such as the following:
750
+     *
751
+     * [
752
+     *   'syncToken' => 'The current synctoken',
753
+     *   'added'   => [
754
+     *      'new.txt',
755
+     *   ],
756
+     *   'modified'   => [
757
+     *      'modified.txt',
758
+     *   ],
759
+     *   'deleted' => [
760
+     *      'foo.php.bak',
761
+     *      'old.txt'
762
+     *   ]
763
+     * ];
764
+     *
765
+     * The returned syncToken property should reflect the *current* syncToken
766
+     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
767
+     * property. This is needed here too, to ensure the operation is atomic.
768
+     *
769
+     * If the $syncToken argument is specified as null, this is an initial
770
+     * sync, and all members should be reported.
771
+     *
772
+     * The modified property is an array of nodenames that have changed since
773
+     * the last token.
774
+     *
775
+     * The deleted property is an array with nodenames, that have been deleted
776
+     * from collection.
777
+     *
778
+     * The $syncLevel argument is basically the 'depth' of the report. If it's
779
+     * 1, you only have to report changes that happened only directly in
780
+     * immediate descendants. If it's 2, it should also include changes from
781
+     * the nodes below the child collections. (grandchildren)
782
+     *
783
+     * The $limit argument allows a client to specify how many results should
784
+     * be returned at most. If the limit is not specified, it should be treated
785
+     * as infinite.
786
+     *
787
+     * If the limit (infinite or not) is higher than you're willing to return,
788
+     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
789
+     *
790
+     * If the syncToken is expired (due to data cleanup) or unknown, you must
791
+     * return null.
792
+     *
793
+     * The limit is 'suggestive'. You are free to ignore it.
794
+     *
795
+     * @param string $addressBookId
796
+     * @param string $syncToken
797
+     * @param int $syncLevel
798
+     * @param int $limit
799
+     * @return array
800
+     */
801
+    function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
802
+        // Current synctoken
803
+        $stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
804
+        $stmt->execute([ $addressBookId ]);
805
+        $currentToken = $stmt->fetchColumn(0);
806
+
807
+        if (is_null($currentToken)) return null;
808
+
809
+        $result = [
810
+            'syncToken' => $currentToken,
811
+            'added'     => [],
812
+            'modified'  => [],
813
+            'deleted'   => [],
814
+        ];
815
+
816
+        if ($syncToken) {
817
+
818
+            $query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
819
+            if ($limit>0) {
820
+                $query .= " LIMIT " . (int)$limit;
821
+            }
822
+
823
+            // Fetching all changes
824
+            $stmt = $this->db->prepare($query);
825
+            $stmt->execute([$syncToken, $currentToken, $addressBookId]);
826
+
827
+            $changes = [];
828
+
829
+            // This loop ensures that any duplicates are overwritten, only the
830
+            // last change on a node is relevant.
831
+            while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
832
+
833
+                $changes[$row['uri']] = $row['operation'];
834
+
835
+            }
836
+
837
+            foreach($changes as $uri => $operation) {
838
+
839
+                switch($operation) {
840
+                    case 1:
841
+                        $result['added'][] = $uri;
842
+                        break;
843
+                    case 2:
844
+                        $result['modified'][] = $uri;
845
+                        break;
846
+                    case 3:
847
+                        $result['deleted'][] = $uri;
848
+                        break;
849
+                }
850
+
851
+            }
852
+        } else {
853
+            // No synctoken supplied, this is the initial sync.
854
+            $query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
855
+            $stmt = $this->db->prepare($query);
856
+            $stmt->execute([$addressBookId]);
857
+
858
+            $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
859
+        }
860
+        return $result;
861
+    }
862
+
863
+    /**
864
+     * Adds a change record to the addressbookchanges table.
865
+     *
866
+     * @param mixed $addressBookId
867
+     * @param string $objectUri
868
+     * @param int $operation 1 = add, 2 = modify, 3 = delete
869
+     * @return void
870
+     */
871
+    protected function addChange($addressBookId, $objectUri, $operation) {
872
+        $sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
873
+        $stmt = $this->db->prepare($sql);
874
+        $stmt->execute([
875
+            $objectUri,
876
+            $addressBookId,
877
+            $operation,
878
+            $addressBookId
879
+        ]);
880
+        $stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
881
+        $stmt->execute([
882
+            $addressBookId
883
+        ]);
884
+    }
885
+
886
+    private function readBlob($cardData) {
887
+        if (is_resource($cardData)) {
888
+            return stream_get_contents($cardData);
889
+        }
890
+
891
+        return $cardData;
892
+    }
893
+
894
+    /**
895
+     * @param IShareable $shareable
896
+     * @param string[] $add
897
+     * @param string[] $remove
898
+     */
899
+    public function updateShares(IShareable $shareable, $add, $remove) {
900
+        $this->sharingBackend->updateShares($shareable, $add, $remove);
901
+    }
902
+
903
+    /**
904
+     * search contact
905
+     *
906
+     * @param int $addressBookId
907
+     * @param string $pattern which should match within the $searchProperties
908
+     * @param array $searchProperties defines the properties within the query pattern should match
909
+     * @param array $options = array() to define the search behavior
910
+     * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are
911
+     * @return array an array of contacts which are arrays of key-value-pairs
912
+     */
913
+    public function search($addressBookId, $pattern, $searchProperties, $options = []) {
914
+        $query = $this->db->getQueryBuilder();
915
+        $query2 = $this->db->getQueryBuilder();
916
+
917
+        $query2->selectDistinct('cp.cardid')->from($this->dbCardsPropertiesTable, 'cp');
918
+        $query2->andWhere($query2->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId)));
919
+        $or = $query2->expr()->orX();
920
+        foreach ($searchProperties as $property) {
921
+            $or->add($query2->expr()->eq('cp.name', $query->createNamedParameter($property)));
922
+        }
923
+        $query2->andWhere($or);
924
+
925
+        // No need for like when the pattern is empty
926
+        if ('' !== $pattern) {
927
+            if(\array_key_exists('escape_like_param', $options) && $options['escape_like_param'] === false) {
928
+                $query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter($pattern)));
929
+            } else {
930
+                $query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
931
+            }
932
+        }
933
+
934
+        $query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c')
935
+            ->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL())));
936
+
937
+        $result = $query->execute();
938
+        $cards = $result->fetchAll();
939
+
940
+        $result->closeCursor();
941
+
942
+        return array_map(function ($array) {
943
+            $array['carddata'] = $this->readBlob($array['carddata']);
944
+            return $array;
945
+        }, $cards);
946
+    }
947
+
948
+    /**
949
+     * @param int $bookId
950
+     * @param string $name
951
+     * @return array
952
+     */
953
+    public function collectCardProperties($bookId, $name) {
954
+        $query = $this->db->getQueryBuilder();
955
+        $result = $query->selectDistinct('value')
956
+            ->from($this->dbCardsPropertiesTable)
957
+            ->where($query->expr()->eq('name', $query->createNamedParameter($name)))
958
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
959
+            ->execute();
960
+
961
+        $all = $result->fetchAll(PDO::FETCH_COLUMN);
962
+        $result->closeCursor();
963
+
964
+        return $all;
965
+    }
966
+
967
+    /**
968
+     * get URI from a given contact
969
+     *
970
+     * @param int $id
971
+     * @return string
972
+     */
973
+    public function getCardUri($id) {
974
+        $query = $this->db->getQueryBuilder();
975
+        $query->select('uri')->from($this->dbCardsTable)
976
+                ->where($query->expr()->eq('id', $query->createParameter('id')))
977
+                ->setParameter('id', $id);
978
+
979
+        $result = $query->execute();
980
+        $uri = $result->fetch();
981
+        $result->closeCursor();
982
+
983
+        if (!isset($uri['uri'])) {
984
+            throw new \InvalidArgumentException('Card does not exists: ' . $id);
985
+        }
986
+
987
+        return $uri['uri'];
988
+    }
989
+
990
+    /**
991
+     * return contact with the given URI
992
+     *
993
+     * @param int $addressBookId
994
+     * @param string $uri
995
+     * @returns array
996
+     */
997
+    public function getContact($addressBookId, $uri) {
998
+        $result = [];
999
+        $query = $this->db->getQueryBuilder();
1000
+        $query->select('*')->from($this->dbCardsTable)
1001
+                ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1002
+                ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1003
+        $queryResult = $query->execute();
1004
+        $contact = $queryResult->fetch();
1005
+        $queryResult->closeCursor();
1006
+
1007
+        if (is_array($contact)) {
1008
+            $result = $contact;
1009
+        }
1010
+
1011
+        return $result;
1012
+    }
1013
+
1014
+    /**
1015
+     * Returns the list of people whom this address book is shared with.
1016
+     *
1017
+     * Every element in this array should have the following properties:
1018
+     *   * href - Often a mailto: address
1019
+     *   * commonName - Optional, for example a first + last name
1020
+     *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
1021
+     *   * readOnly - boolean
1022
+     *   * summary - Optional, a description for the share
1023
+     *
1024
+     * @return array
1025
+     */
1026
+    public function getShares($addressBookId) {
1027
+        return $this->sharingBackend->getShares($addressBookId);
1028
+    }
1029
+
1030
+    /**
1031
+     * update properties table
1032
+     *
1033
+     * @param int $addressBookId
1034
+     * @param string $cardUri
1035
+     * @param string $vCardSerialized
1036
+     */
1037
+    protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1038
+        $cardId = $this->getCardId($addressBookId, $cardUri);
1039
+        $vCard = $this->readCard($vCardSerialized);
1040
+
1041
+        $this->purgeProperties($addressBookId, $cardId);
1042
+
1043
+        $query = $this->db->getQueryBuilder();
1044
+        $query->insert($this->dbCardsPropertiesTable)
1045
+            ->values(
1046
+                [
1047
+                    'addressbookid' => $query->createNamedParameter($addressBookId),
1048
+                    'cardid' => $query->createNamedParameter($cardId),
1049
+                    'name' => $query->createParameter('name'),
1050
+                    'value' => $query->createParameter('value'),
1051
+                    'preferred' => $query->createParameter('preferred')
1052
+                ]
1053
+            );
1054
+
1055
+        foreach ($vCard->children() as $property) {
1056
+            if(!in_array($property->name, self::$indexProperties)) {
1057
+                continue;
1058
+            }
1059
+            $preferred = 0;
1060
+            foreach($property->parameters as $parameter) {
1061
+                if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1062
+                    $preferred = 1;
1063
+                    break;
1064
+                }
1065
+            }
1066
+            $query->setParameter('name', $property->name);
1067
+            $query->setParameter('value', mb_substr($property->getValue(), 0, 254));
1068
+            $query->setParameter('preferred', $preferred);
1069
+            $query->execute();
1070
+        }
1071
+    }
1072
+
1073
+    /**
1074
+     * read vCard data into a vCard object
1075
+     *
1076
+     * @param string $cardData
1077
+     * @return VCard
1078
+     */
1079
+    protected function readCard($cardData) {
1080
+        return  Reader::read($cardData);
1081
+    }
1082
+
1083
+    /**
1084
+     * delete all properties from a given card
1085
+     *
1086
+     * @param int $addressBookId
1087
+     * @param int $cardId
1088
+     */
1089
+    protected function purgeProperties($addressBookId, $cardId) {
1090
+        $query = $this->db->getQueryBuilder();
1091
+        $query->delete($this->dbCardsPropertiesTable)
1092
+            ->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1093
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1094
+        $query->execute();
1095
+    }
1096
+
1097
+    /**
1098
+     * get ID from a given contact
1099
+     *
1100
+     * @param int $addressBookId
1101
+     * @param string $uri
1102
+     * @return int
1103
+     */
1104
+    protected function getCardId($addressBookId, $uri) {
1105
+        $query = $this->db->getQueryBuilder();
1106
+        $query->select('id')->from($this->dbCardsTable)
1107
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1108
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1109
+
1110
+        $result = $query->execute();
1111
+        $cardIds = $result->fetch();
1112
+        $result->closeCursor();
1113
+
1114
+        if (!isset($cardIds['id'])) {
1115
+            throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1116
+        }
1117
+
1118
+        return (int)$cardIds['id'];
1119
+    }
1120
+
1121
+    /**
1122
+     * For shared address books the sharee is set in the ACL of the address book
1123
+     * @param $addressBookId
1124
+     * @param $acl
1125
+     * @return array
1126
+     */
1127
+    public function applyShareAcl($addressBookId, $acl) {
1128
+        return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1129
+    }
1130
+
1131
+    private function convertPrincipal($principalUri, $toV2) {
1132
+        if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1133
+            list(, $name) = \Sabre\Uri\split($principalUri);
1134
+            if ($toV2 === true) {
1135
+                return "principals/users/$name";
1136
+            }
1137
+            return "principals/$name";
1138
+        }
1139
+        return $principalUri;
1140
+    }
1141
+
1142
+    private function addOwnerPrincipal(&$addressbookInfo) {
1143
+        $ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1144
+        $displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1145
+        if (isset($addressbookInfo[$ownerPrincipalKey])) {
1146
+            $uri = $addressbookInfo[$ownerPrincipalKey];
1147
+        } else {
1148
+            $uri = $addressbookInfo['principaluri'];
1149
+        }
1150
+
1151
+        $principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1152
+        if (isset($principalInformation['{DAV:}displayname'])) {
1153
+            $addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1154
+        }
1155
+    }
1156
+
1157
+    /**
1158
+     * Extract UID from vcard
1159
+     *
1160
+     * @param string $cardData the vcard raw data
1161
+     * @return string the uid
1162
+     * @throws BadRequest if no UID is available
1163
+     */
1164
+    private function getUID($cardData) {
1165
+        if ($cardData != '') {
1166
+            $vCard = Reader::read($cardData);
1167
+            if ($vCard->UID) {
1168
+                $uid = $vCard->UID->getValue();
1169
+                return $uid;
1170
+            }
1171
+            // should already be handled, but just in case
1172
+            throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
1173
+        }
1174
+        // should already be handled, but just in case
1175
+        throw new BadRequest('vCard can not be empty');
1176
+    }
1177 1177
 }
Please login to merge, or discard this patch.
apps/cloud_federation_api/lib/Controller/RequestHandlerController.php 1 patch
Indentation   +244 added lines, -244 removed lines patch added patch discarded remove patch
@@ -53,249 +53,249 @@
 block discarded – undo
53 53
  */
54 54
 class RequestHandlerController extends Controller {
55 55
 
56
-	/** @var ILogger */
57
-	private $logger;
58
-
59
-	/** @var IUserManager */
60
-	private $userManager;
61
-
62
-	/** @var IGroupManager */
63
-	private $groupManager;
64
-
65
-	/** @var IURLGenerator */
66
-	private $urlGenerator;
67
-
68
-	/** @var ICloudFederationProviderManager */
69
-	private $cloudFederationProviderManager;
70
-
71
-	/** @var Config */
72
-	private $config;
73
-
74
-	/** @var ICloudFederationFactory */
75
-	private $factory;
76
-
77
-	/** @var ICloudIdManager */
78
-	private $cloudIdManager;
79
-
80
-	public function __construct($appName,
81
-								IRequest $request,
82
-								ILogger $logger,
83
-								IUserManager $userManager,
84
-								IGroupManager $groupManager,
85
-								IURLGenerator $urlGenerator,
86
-								ICloudFederationProviderManager $cloudFederationProviderManager,
87
-								Config $config,
88
-								ICloudFederationFactory $factory,
89
-								ICloudIdManager $cloudIdManager
90
-	) {
91
-		parent::__construct($appName, $request);
92
-
93
-		$this->logger = $logger;
94
-		$this->userManager = $userManager;
95
-		$this->groupManager = $groupManager;
96
-		$this->urlGenerator = $urlGenerator;
97
-		$this->cloudFederationProviderManager = $cloudFederationProviderManager;
98
-		$this->config = $config;
99
-		$this->factory = $factory;
100
-		$this->cloudIdManager = $cloudIdManager;
101
-	}
102
-
103
-	/**
104
-	 * add share
105
-	 *
106
-	 * @NoCSRFRequired
107
-	 * @PublicPage
108
-	 * @BruteForceProtection(action=receiveFederatedShare)
109
-	 *
110
-	 * @param string $shareWith
111
-	 * @param string $name resource name (e.g. document.odt)
112
-	 * @param string $description share description (optional)
113
-	 * @param string $providerId resource UID on the provider side
114
-	 * @param string $owner provider specific UID of the user who owns the resource
115
-	 * @param string $ownerDisplayName display name of the user who shared the item
116
-	 * @param string $sharedBy provider specific UID of the user who shared the resource
117
-	 * @param string $sharedByDisplayName display name of the user who shared the resource
118
-	 * @param array $protocol (e,.g. ['name' => 'webdav', 'options' => ['username' => 'john', 'permissions' => 31]])
119
-	 * @param string $shareType ('group' or 'user' share)
120
-	 * @param $resourceType ('file', 'calendar',...)
121
-	 * @return Http\DataResponse|JSONResponse
122
-	 *
123
-	 * Example: curl -H "Content-Type: application/json" -X POST -d '{"shareWith":"admin1@serve1","name":"welcome server2.txt","description":"desc","providerId":"2","owner":"admin2@http://localhost/server2","ownerDisplayName":"admin2 display","shareType":"user","resourceType":"file","protocol":{"name":"webdav","options":{"sharedSecret":"secret","permissions":"webdav-property"}}}' http://localhost/server/index.php/ocm/shares
124
-	 */
125
-	public function addShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, $protocol, $shareType, $resourceType) {
126
-
127
-		// check if all required parameters are set
128
-		if ($shareWith === null ||
129
-			$name === null ||
130
-			$providerId === null ||
131
-			$owner === null ||
132
-			$resourceType === null ||
133
-			$shareType === null ||
134
-			!is_array($protocol) ||
135
-			!isset($protocol['name']) ||
136
-			!isset($protocol['options']) ||
137
-			!is_array($protocol['options']) ||
138
-			!isset($protocol['options']['sharedSecret'])
139
-		) {
140
-			return new JSONResponse(
141
-				['message' => 'Missing arguments'],
142
-				Http::STATUS_BAD_REQUEST
143
-			);
144
-		}
145
-
146
-		$supportedShareTypes = $this->config->getSupportedShareTypes($resourceType);
147
-		if (!in_array($shareType, $supportedShareTypes)) {
148
-			return new JSONResponse(
149
-				['message' => 'Share type "' . $shareType . '" not implemented'],
150
-				Http::STATUS_NOT_IMPLEMENTED
151
-			);
152
-		}
153
-
154
-		$cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
155
-		$shareWith = $cloudId->getUser();
156
-
157
-		if ($shareType === 'user') {
158
-			$shareWith = $this->mapUid($shareWith);
159
-
160
-			if (!$this->userManager->userExists($shareWith)) {
161
-				return new JSONResponse(
162
-					['message' => 'User "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl()],
163
-					Http::STATUS_BAD_REQUEST
164
-				);
165
-			}
166
-		}
167
-
168
-		if ($shareType === 'group') {
169
-			if(!$this->groupManager->groupExists($shareWith)) {
170
-				return new JSONResponse(
171
-					['message' => 'Group "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl()],
172
-					Http::STATUS_BAD_REQUEST
173
-				);
174
-			}
175
-		}
176
-
177
-		// if no explicit display name is given, we use the uid as display name
178
-		$ownerDisplayName = $ownerDisplayName === null ? $owner : $ownerDisplayName;
179
-		$sharedByDisplayName = $sharedByDisplayName === null ? $sharedBy : $sharedByDisplayName;
180
-
181
-		// sharedBy* parameter is optional, if nothing is set we assume that it is the same user as the owner
182
-		if ($sharedBy === null) {
183
-			$sharedBy = $owner;
184
-			$sharedByDisplayName = $ownerDisplayName;
185
-		}
186
-
187
-		try {
188
-			$provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
189
-			$share = $this->factory->getCloudFederationShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, '', $shareType, $resourceType);
190
-			$share->setProtocol($protocol);
191
-			$provider->shareReceived($share);
192
-		} catch (ProviderDoesNotExistsException $e) {
193
-			return new JSONResponse(
194
-				['message' => $e->getMessage()],
195
-				Http::STATUS_NOT_IMPLEMENTED
196
-			);
197
-		} catch (ProviderCouldNotAddShareException $e) {
198
-			return new JSONResponse(
199
-				['message' => $e->getMessage()],
200
-				$e->getCode()
201
-			);
202
-		} catch (\Exception $e) {
203
-			return new JSONResponse(
204
-				['message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl()],
205
-				Http::STATUS_BAD_REQUEST
206
-			);
207
-		}
208
-
209
-		$user = $this->userManager->get($shareWith);
210
-		$recipientDisplayName = '';
211
-		if($user) {
212
-			$recipientDisplayName = $user->getDisplayName();
213
-		}
214
-
215
-		return new JSONResponse(
216
-			['recipientDisplayName' => $recipientDisplayName],
217
-			Http::STATUS_CREATED);
218
-
219
-	}
220
-
221
-	/**
222
-	 * receive notification about existing share
223
-	 *
224
-	 * @NoCSRFRequired
225
-	 * @PublicPage
226
-	 * @BruteForceProtection(action=receiveFederatedShareNotification)
227
-	 *
228
-	 * @param string $notificationType (notification type, e.g. SHARE_ACCEPTED)
229
-	 * @param string $resourceType (calendar, file, contact,...)
230
-	 * @param string $providerId id of the share
231
-	 * @param array $notification the actual payload of the notification
232
-	 * @return JSONResponse
233
-	 */
234
-	public function receiveNotification($notificationType, $resourceType, $providerId, array $notification) {
235
-
236
-		// check if all required parameters are set
237
-		if ($notificationType === null ||
238
-			$resourceType === null ||
239
-			$providerId === null ||
240
-			!is_array($notification)
241
-		) {
242
-			return new JSONResponse(
243
-				['message' => 'Missing arguments'],
244
-				Http::STATUS_BAD_REQUEST
245
-			);
246
-		}
247
-
248
-		try {
249
-			$provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
250
-			$result = $provider->notificationReceived($notificationType, $providerId, $notification);
251
-		} catch (ProviderDoesNotExistsException $e) {
252
-			return new JSONResponse(
253
-				['message' => $e->getMessage()],
254
-				Http::STATUS_BAD_REQUEST
255
-			);
256
-		} catch (ShareNotFound $e) {
257
-			return new JSONResponse(
258
-				['message' => $e->getMessage()],
259
-				Http::STATUS_BAD_REQUEST
260
-			);
261
-		} catch (ActionNotSupportedException $e) {
262
-			return new JSONResponse(
263
-				['message' => $e->getMessage()],
264
-				Http::STATUS_NOT_IMPLEMENTED
265
-			);
266
-		} catch (BadRequestException $e) {
267
-			return new JSONResponse($e->getReturnMessage(), Http::STATUS_BAD_REQUEST);
268
-		} catch (AuthenticationFailedException $e) {
269
-			return new JSONResponse(["message" => "RESOURCE_NOT_FOUND"], Http::STATUS_FORBIDDEN);
270
-		}
271
-		catch (\Exception $e) {
272
-			return new JSONResponse(
273
-				['message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl()],
274
-				Http::STATUS_BAD_REQUEST
275
-			);
276
-		}
277
-
278
-		return new JSONResponse($result,Http::STATUS_CREATED);
279
-
280
-	}
281
-
282
-	/**
283
-	 * map login name to internal LDAP UID if a LDAP backend is in use
284
-	 *
285
-	 * @param string $uid
286
-	 * @return string mixed
287
-	 */
288
-	private function mapUid($uid) {
289
-		// FIXME this should be a method in the user management instead
290
-		$this->logger->debug('shareWith before, ' . $uid, ['app' => $this->appName]);
291
-		\OCP\Util::emitHook(
292
-			'\OCA\Files_Sharing\API\Server2Server',
293
-			'preLoginNameUsedAsUserName',
294
-			['uid' => &$uid]
295
-		);
296
-		$this->logger->debug('shareWith after, ' . $uid, ['app' => $this->appName]);
297
-
298
-		return $uid;
299
-	}
56
+    /** @var ILogger */
57
+    private $logger;
58
+
59
+    /** @var IUserManager */
60
+    private $userManager;
61
+
62
+    /** @var IGroupManager */
63
+    private $groupManager;
64
+
65
+    /** @var IURLGenerator */
66
+    private $urlGenerator;
67
+
68
+    /** @var ICloudFederationProviderManager */
69
+    private $cloudFederationProviderManager;
70
+
71
+    /** @var Config */
72
+    private $config;
73
+
74
+    /** @var ICloudFederationFactory */
75
+    private $factory;
76
+
77
+    /** @var ICloudIdManager */
78
+    private $cloudIdManager;
79
+
80
+    public function __construct($appName,
81
+                                IRequest $request,
82
+                                ILogger $logger,
83
+                                IUserManager $userManager,
84
+                                IGroupManager $groupManager,
85
+                                IURLGenerator $urlGenerator,
86
+                                ICloudFederationProviderManager $cloudFederationProviderManager,
87
+                                Config $config,
88
+                                ICloudFederationFactory $factory,
89
+                                ICloudIdManager $cloudIdManager
90
+    ) {
91
+        parent::__construct($appName, $request);
92
+
93
+        $this->logger = $logger;
94
+        $this->userManager = $userManager;
95
+        $this->groupManager = $groupManager;
96
+        $this->urlGenerator = $urlGenerator;
97
+        $this->cloudFederationProviderManager = $cloudFederationProviderManager;
98
+        $this->config = $config;
99
+        $this->factory = $factory;
100
+        $this->cloudIdManager = $cloudIdManager;
101
+    }
102
+
103
+    /**
104
+     * add share
105
+     *
106
+     * @NoCSRFRequired
107
+     * @PublicPage
108
+     * @BruteForceProtection(action=receiveFederatedShare)
109
+     *
110
+     * @param string $shareWith
111
+     * @param string $name resource name (e.g. document.odt)
112
+     * @param string $description share description (optional)
113
+     * @param string $providerId resource UID on the provider side
114
+     * @param string $owner provider specific UID of the user who owns the resource
115
+     * @param string $ownerDisplayName display name of the user who shared the item
116
+     * @param string $sharedBy provider specific UID of the user who shared the resource
117
+     * @param string $sharedByDisplayName display name of the user who shared the resource
118
+     * @param array $protocol (e,.g. ['name' => 'webdav', 'options' => ['username' => 'john', 'permissions' => 31]])
119
+     * @param string $shareType ('group' or 'user' share)
120
+     * @param $resourceType ('file', 'calendar',...)
121
+     * @return Http\DataResponse|JSONResponse
122
+     *
123
+     * Example: curl -H "Content-Type: application/json" -X POST -d '{"shareWith":"admin1@serve1","name":"welcome server2.txt","description":"desc","providerId":"2","owner":"admin2@http://localhost/server2","ownerDisplayName":"admin2 display","shareType":"user","resourceType":"file","protocol":{"name":"webdav","options":{"sharedSecret":"secret","permissions":"webdav-property"}}}' http://localhost/server/index.php/ocm/shares
124
+     */
125
+    public function addShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, $protocol, $shareType, $resourceType) {
126
+
127
+        // check if all required parameters are set
128
+        if ($shareWith === null ||
129
+            $name === null ||
130
+            $providerId === null ||
131
+            $owner === null ||
132
+            $resourceType === null ||
133
+            $shareType === null ||
134
+            !is_array($protocol) ||
135
+            !isset($protocol['name']) ||
136
+            !isset($protocol['options']) ||
137
+            !is_array($protocol['options']) ||
138
+            !isset($protocol['options']['sharedSecret'])
139
+        ) {
140
+            return new JSONResponse(
141
+                ['message' => 'Missing arguments'],
142
+                Http::STATUS_BAD_REQUEST
143
+            );
144
+        }
145
+
146
+        $supportedShareTypes = $this->config->getSupportedShareTypes($resourceType);
147
+        if (!in_array($shareType, $supportedShareTypes)) {
148
+            return new JSONResponse(
149
+                ['message' => 'Share type "' . $shareType . '" not implemented'],
150
+                Http::STATUS_NOT_IMPLEMENTED
151
+            );
152
+        }
153
+
154
+        $cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
155
+        $shareWith = $cloudId->getUser();
156
+
157
+        if ($shareType === 'user') {
158
+            $shareWith = $this->mapUid($shareWith);
159
+
160
+            if (!$this->userManager->userExists($shareWith)) {
161
+                return new JSONResponse(
162
+                    ['message' => 'User "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl()],
163
+                    Http::STATUS_BAD_REQUEST
164
+                );
165
+            }
166
+        }
167
+
168
+        if ($shareType === 'group') {
169
+            if(!$this->groupManager->groupExists($shareWith)) {
170
+                return new JSONResponse(
171
+                    ['message' => 'Group "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl()],
172
+                    Http::STATUS_BAD_REQUEST
173
+                );
174
+            }
175
+        }
176
+
177
+        // if no explicit display name is given, we use the uid as display name
178
+        $ownerDisplayName = $ownerDisplayName === null ? $owner : $ownerDisplayName;
179
+        $sharedByDisplayName = $sharedByDisplayName === null ? $sharedBy : $sharedByDisplayName;
180
+
181
+        // sharedBy* parameter is optional, if nothing is set we assume that it is the same user as the owner
182
+        if ($sharedBy === null) {
183
+            $sharedBy = $owner;
184
+            $sharedByDisplayName = $ownerDisplayName;
185
+        }
186
+
187
+        try {
188
+            $provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
189
+            $share = $this->factory->getCloudFederationShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, '', $shareType, $resourceType);
190
+            $share->setProtocol($protocol);
191
+            $provider->shareReceived($share);
192
+        } catch (ProviderDoesNotExistsException $e) {
193
+            return new JSONResponse(
194
+                ['message' => $e->getMessage()],
195
+                Http::STATUS_NOT_IMPLEMENTED
196
+            );
197
+        } catch (ProviderCouldNotAddShareException $e) {
198
+            return new JSONResponse(
199
+                ['message' => $e->getMessage()],
200
+                $e->getCode()
201
+            );
202
+        } catch (\Exception $e) {
203
+            return new JSONResponse(
204
+                ['message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl()],
205
+                Http::STATUS_BAD_REQUEST
206
+            );
207
+        }
208
+
209
+        $user = $this->userManager->get($shareWith);
210
+        $recipientDisplayName = '';
211
+        if($user) {
212
+            $recipientDisplayName = $user->getDisplayName();
213
+        }
214
+
215
+        return new JSONResponse(
216
+            ['recipientDisplayName' => $recipientDisplayName],
217
+            Http::STATUS_CREATED);
218
+
219
+    }
220
+
221
+    /**
222
+     * receive notification about existing share
223
+     *
224
+     * @NoCSRFRequired
225
+     * @PublicPage
226
+     * @BruteForceProtection(action=receiveFederatedShareNotification)
227
+     *
228
+     * @param string $notificationType (notification type, e.g. SHARE_ACCEPTED)
229
+     * @param string $resourceType (calendar, file, contact,...)
230
+     * @param string $providerId id of the share
231
+     * @param array $notification the actual payload of the notification
232
+     * @return JSONResponse
233
+     */
234
+    public function receiveNotification($notificationType, $resourceType, $providerId, array $notification) {
235
+
236
+        // check if all required parameters are set
237
+        if ($notificationType === null ||
238
+            $resourceType === null ||
239
+            $providerId === null ||
240
+            !is_array($notification)
241
+        ) {
242
+            return new JSONResponse(
243
+                ['message' => 'Missing arguments'],
244
+                Http::STATUS_BAD_REQUEST
245
+            );
246
+        }
247
+
248
+        try {
249
+            $provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
250
+            $result = $provider->notificationReceived($notificationType, $providerId, $notification);
251
+        } catch (ProviderDoesNotExistsException $e) {
252
+            return new JSONResponse(
253
+                ['message' => $e->getMessage()],
254
+                Http::STATUS_BAD_REQUEST
255
+            );
256
+        } catch (ShareNotFound $e) {
257
+            return new JSONResponse(
258
+                ['message' => $e->getMessage()],
259
+                Http::STATUS_BAD_REQUEST
260
+            );
261
+        } catch (ActionNotSupportedException $e) {
262
+            return new JSONResponse(
263
+                ['message' => $e->getMessage()],
264
+                Http::STATUS_NOT_IMPLEMENTED
265
+            );
266
+        } catch (BadRequestException $e) {
267
+            return new JSONResponse($e->getReturnMessage(), Http::STATUS_BAD_REQUEST);
268
+        } catch (AuthenticationFailedException $e) {
269
+            return new JSONResponse(["message" => "RESOURCE_NOT_FOUND"], Http::STATUS_FORBIDDEN);
270
+        }
271
+        catch (\Exception $e) {
272
+            return new JSONResponse(
273
+                ['message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl()],
274
+                Http::STATUS_BAD_REQUEST
275
+            );
276
+        }
277
+
278
+        return new JSONResponse($result,Http::STATUS_CREATED);
279
+
280
+    }
281
+
282
+    /**
283
+     * map login name to internal LDAP UID if a LDAP backend is in use
284
+     *
285
+     * @param string $uid
286
+     * @return string mixed
287
+     */
288
+    private function mapUid($uid) {
289
+        // FIXME this should be a method in the user management instead
290
+        $this->logger->debug('shareWith before, ' . $uid, ['app' => $this->appName]);
291
+        \OCP\Util::emitHook(
292
+            '\OCA\Files_Sharing\API\Server2Server',
293
+            'preLoginNameUsedAsUserName',
294
+            ['uid' => &$uid]
295
+        );
296
+        $this->logger->debug('shareWith after, ' . $uid, ['app' => $this->appName]);
297
+
298
+        return $uid;
299
+    }
300 300
 
301 301
 }
Please login to merge, or discard this patch.
apps/files/lib/Command/Scan.php 1 patch
Indentation   +270 added lines, -270 removed lines patch added patch discarded remove patch
@@ -51,275 +51,275 @@
 block discarded – undo
51 51
 
52 52
 class Scan extends Base {
53 53
 
54
-	/** @var IUserManager $userManager */
55
-	private $userManager;
56
-	/** @var float */
57
-	protected $execTime = 0;
58
-	/** @var int */
59
-	protected $foldersCounter = 0;
60
-	/** @var int */
61
-	protected $filesCounter = 0;
62
-
63
-	public function __construct(IUserManager $userManager) {
64
-		$this->userManager = $userManager;
65
-		parent::__construct();
66
-	}
67
-
68
-	protected function configure() {
69
-		parent::configure();
70
-
71
-		$this
72
-			->setName('files:scan')
73
-			->setDescription('rescan filesystem')
74
-			->addArgument(
75
-				'user_id',
76
-				InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
77
-				'will rescan all files of the given user(s)'
78
-			)
79
-			->addOption(
80
-				'path',
81
-				'p',
82
-				InputArgument::OPTIONAL,
83
-				'limit rescan to this path, eg. --path="/alice/files/Music", the user_id is determined by the path and the user_id parameter and --all are ignored'
84
-			)
85
-			->addOption(
86
-				'all',
87
-				null,
88
-				InputOption::VALUE_NONE,
89
-				'will rescan all files of all known users'
90
-			)->addOption(
91
-				'unscanned',
92
-				null,
93
-				InputOption::VALUE_NONE,
94
-				'only scan files which are marked as not fully scanned'
95
-			)->addOption(
96
-				'shallow',
97
-				null,
98
-				InputOption::VALUE_NONE,
99
-				'do not scan folders recursively'
100
-			)->addOption(
101
-				'home-only',
102
-				null,
103
-				InputOption::VALUE_NONE,
104
-				'only scan the home storage, ignoring any mounted external storage or share'
105
-			);
106
-	}
107
-
108
-	public function checkScanWarning($fullPath, OutputInterface $output) {
109
-		$normalizedPath = basename(\OC\Files\Filesystem::normalizePath($fullPath));
110
-		$path = basename($fullPath);
111
-
112
-		if ($normalizedPath !== $path) {
113
-			$output->writeln("\t<error>Entry \"" . $fullPath . '" will not be accessible due to incompatible encoding</error>');
114
-		}
115
-	}
116
-
117
-	protected function scanFiles($user, $path, OutputInterface $output, $backgroundScan = false, $recursive = true, $homeOnly = false) {
118
-		$connection = $this->reconnectToDatabase($output);
119
-		$scanner = new \OC\Files\Utils\Scanner($user, $connection, \OC::$server->query(IEventDispatcher::class), \OC::$server->getLogger());
120
-
121
-		# check on each file/folder if there was a user interrupt (ctrl-c) and throw an exception
122
-
123
-		$scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function ($path) use ($output) {
124
-			$output->writeln("\tFile\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE);
125
-			++$this->filesCounter;
126
-			$this->abortIfInterrupted();
127
-		});
128
-
129
-		$scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) {
130
-			$output->writeln("\tFolder\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE);
131
-			++$this->foldersCounter;
132
-			$this->abortIfInterrupted();
133
-		});
134
-
135
-		$scanner->listen('\OC\Files\Utils\Scanner', 'StorageNotAvailable', function (StorageNotAvailableException $e) use ($output) {
136
-			$output->writeln('Error while scanning, storage not available (' . $e->getMessage() . ')', OutputInterface::VERBOSITY_VERBOSE);
137
-		});
138
-
139
-		$scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function ($path) use ($output) {
140
-			$this->checkScanWarning($path, $output);
141
-		});
142
-
143
-		$scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) {
144
-			$this->checkScanWarning($path, $output);
145
-		});
146
-
147
-		try {
148
-			if ($backgroundScan) {
149
-				$scanner->backgroundScan($path);
150
-			} else {
151
-				$scanner->scan($path, $recursive, $homeOnly ? [$this, 'filterHomeMount'] : null);
152
-			}
153
-		} catch (ForbiddenException $e) {
154
-			$output->writeln("<error>Home storage for user $user not writable</error>");
155
-			$output->writeln('Make sure you\'re running the scan command only as the user the web server runs as');
156
-		} catch (InterruptedException $e) {
157
-			# exit the function if ctrl-c has been pressed
158
-			$output->writeln('Interrupted by user');
159
-		} catch (NotFoundException $e) {
160
-			$output->writeln('<error>Path not found: ' . $e->getMessage() . '</error>');
161
-		} catch (\Exception $e) {
162
-			$output->writeln('<error>Exception during scan: ' . $e->getMessage() . '</error>');
163
-			$output->writeln('<error>' . $e->getTraceAsString() . '</error>');
164
-		}
165
-	}
166
-
167
-	public function filterHomeMount(IMountPoint $mountPoint) {
168
-		// any mountpoint inside '/$user/files/'
169
-		return substr_count($mountPoint->getMountPoint(), '/') <= 3;
170
-	}
171
-
172
-	protected function execute(InputInterface $input, OutputInterface $output) {
173
-		$inputPath = $input->getOption('path');
174
-		if ($inputPath) {
175
-			$inputPath = '/' . trim($inputPath, '/');
176
-			list(, $user,) = explode('/', $inputPath, 3);
177
-			$users = [$user];
178
-		} else if ($input->getOption('all')) {
179
-			$users = $this->userManager->search('');
180
-		} else {
181
-			$users = $input->getArgument('user_id');
182
-		}
183
-
184
-		# restrict the verbosity level to VERBOSITY_VERBOSE
185
-		if ($output->getVerbosity() > OutputInterface::VERBOSITY_VERBOSE) {
186
-			$output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
187
-		}
188
-
189
-		# check quantity of users to be process and show it on the command line
190
-		$users_total = count($users);
191
-		if ($users_total === 0) {
192
-			$output->writeln('<error>Please specify the user id to scan, --all to scan for all users or --path=...</error>');
193
-			return;
194
-		}
195
-
196
-		$this->initTools();
197
-
198
-		$user_count = 0;
199
-		foreach ($users as $user) {
200
-			if (is_object($user)) {
201
-				$user = $user->getUID();
202
-			}
203
-			$path = $inputPath ? $inputPath : '/' . $user;
204
-			++$user_count;
205
-			if ($this->userManager->userExists($user)) {
206
-				$output->writeln("Starting scan for user $user_count out of $users_total ($user)");
207
-				$this->scanFiles($user, $path, $output, $input->getOption('unscanned'), !$input->getOption('shallow'), $input->getOption('home-only'));
208
-				$output->writeln('', OutputInterface::VERBOSITY_VERBOSE);
209
-			} else {
210
-				$output->writeln("<error>Unknown user $user_count $user</error>");
211
-				$output->writeln('', OutputInterface::VERBOSITY_VERBOSE);
212
-			}
213
-
214
-			try {
215
-				$this->abortIfInterrupted();
216
-			} catch (InterruptedException $e) {
217
-				break;
218
-			}
219
-		}
220
-
221
-		$this->presentStats($output);
222
-	}
223
-
224
-	/**
225
-	 * Initialises some useful tools for the Command
226
-	 */
227
-	protected function initTools() {
228
-		// Start the timer
229
-		$this->execTime = -microtime(true);
230
-		// Convert PHP errors to exceptions
231
-		set_error_handler([$this, 'exceptionErrorHandler'], E_ALL);
232
-	}
233
-
234
-	/**
235
-	 * Processes PHP errors as exceptions in order to be able to keep track of problems
236
-	 *
237
-	 * @see https://secure.php.net/manual/en/function.set-error-handler.php
238
-	 *
239
-	 * @param int $severity the level of the error raised
240
-	 * @param string $message
241
-	 * @param string $file the filename that the error was raised in
242
-	 * @param int $line the line number the error was raised
243
-	 *
244
-	 * @throws \ErrorException
245
-	 */
246
-	public function exceptionErrorHandler($severity, $message, $file, $line) {
247
-		if (!(error_reporting() & $severity)) {
248
-			// This error code is not included in error_reporting
249
-			return;
250
-		}
251
-		throw new \ErrorException($message, 0, $severity, $file, $line);
252
-	}
253
-
254
-	/**
255
-	 * @param OutputInterface $output
256
-	 */
257
-	protected function presentStats(OutputInterface $output) {
258
-		// Stop the timer
259
-		$this->execTime += microtime(true);
260
-
261
-		$headers = [
262
-			'Folders', 'Files', 'Elapsed time'
263
-		];
264
-
265
-		$this->showSummary($headers, null, $output);
266
-	}
267
-
268
-	/**
269
-	 * Shows a summary of operations
270
-	 *
271
-	 * @param string[] $headers
272
-	 * @param string[] $rows
273
-	 * @param OutputInterface $output
274
-	 */
275
-	protected function showSummary($headers, $rows, OutputInterface $output) {
276
-		$niceDate = $this->formatExecTime();
277
-		if (!$rows) {
278
-			$rows = [
279
-				$this->foldersCounter,
280
-				$this->filesCounter,
281
-				$niceDate,
282
-			];
283
-		}
284
-		$table = new Table($output);
285
-		$table
286
-			->setHeaders($headers)
287
-			->setRows([$rows]);
288
-		$table->render();
289
-	}
290
-
291
-
292
-	/**
293
-	 * Formats microtime into a human readable format
294
-	 *
295
-	 * @return string
296
-	 */
297
-	protected function formatExecTime() {
298
-		$secs = round($this->execTime);
299
-		# convert seconds into HH:MM:SS form
300
-		return sprintf('%02d:%02d:%02d', ($secs/3600), ($secs/60%60), $secs%60);
301
-	}
302
-
303
-	/**
304
-	 * @return \OCP\IDBConnection
305
-	 */
306
-	protected function reconnectToDatabase(OutputInterface $output) {
307
-		/** @var Connection | IDBConnection $connection */
308
-		$connection = \OC::$server->getDatabaseConnection();
309
-		try {
310
-			$connection->close();
311
-		} catch (\Exception $ex) {
312
-			$output->writeln("<info>Error while disconnecting from database: {$ex->getMessage()}</info>");
313
-		}
314
-		while (!$connection->isConnected()) {
315
-			try {
316
-				$connection->connect();
317
-			} catch (\Exception $ex) {
318
-				$output->writeln("<info>Error while re-connecting to database: {$ex->getMessage()}</info>");
319
-				sleep(60);
320
-			}
321
-		}
322
-		return $connection;
323
-	}
54
+    /** @var IUserManager $userManager */
55
+    private $userManager;
56
+    /** @var float */
57
+    protected $execTime = 0;
58
+    /** @var int */
59
+    protected $foldersCounter = 0;
60
+    /** @var int */
61
+    protected $filesCounter = 0;
62
+
63
+    public function __construct(IUserManager $userManager) {
64
+        $this->userManager = $userManager;
65
+        parent::__construct();
66
+    }
67
+
68
+    protected function configure() {
69
+        parent::configure();
70
+
71
+        $this
72
+            ->setName('files:scan')
73
+            ->setDescription('rescan filesystem')
74
+            ->addArgument(
75
+                'user_id',
76
+                InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
77
+                'will rescan all files of the given user(s)'
78
+            )
79
+            ->addOption(
80
+                'path',
81
+                'p',
82
+                InputArgument::OPTIONAL,
83
+                'limit rescan to this path, eg. --path="/alice/files/Music", the user_id is determined by the path and the user_id parameter and --all are ignored'
84
+            )
85
+            ->addOption(
86
+                'all',
87
+                null,
88
+                InputOption::VALUE_NONE,
89
+                'will rescan all files of all known users'
90
+            )->addOption(
91
+                'unscanned',
92
+                null,
93
+                InputOption::VALUE_NONE,
94
+                'only scan files which are marked as not fully scanned'
95
+            )->addOption(
96
+                'shallow',
97
+                null,
98
+                InputOption::VALUE_NONE,
99
+                'do not scan folders recursively'
100
+            )->addOption(
101
+                'home-only',
102
+                null,
103
+                InputOption::VALUE_NONE,
104
+                'only scan the home storage, ignoring any mounted external storage or share'
105
+            );
106
+    }
107
+
108
+    public function checkScanWarning($fullPath, OutputInterface $output) {
109
+        $normalizedPath = basename(\OC\Files\Filesystem::normalizePath($fullPath));
110
+        $path = basename($fullPath);
111
+
112
+        if ($normalizedPath !== $path) {
113
+            $output->writeln("\t<error>Entry \"" . $fullPath . '" will not be accessible due to incompatible encoding</error>');
114
+        }
115
+    }
116
+
117
+    protected function scanFiles($user, $path, OutputInterface $output, $backgroundScan = false, $recursive = true, $homeOnly = false) {
118
+        $connection = $this->reconnectToDatabase($output);
119
+        $scanner = new \OC\Files\Utils\Scanner($user, $connection, \OC::$server->query(IEventDispatcher::class), \OC::$server->getLogger());
120
+
121
+        # check on each file/folder if there was a user interrupt (ctrl-c) and throw an exception
122
+
123
+        $scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function ($path) use ($output) {
124
+            $output->writeln("\tFile\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE);
125
+            ++$this->filesCounter;
126
+            $this->abortIfInterrupted();
127
+        });
128
+
129
+        $scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) {
130
+            $output->writeln("\tFolder\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE);
131
+            ++$this->foldersCounter;
132
+            $this->abortIfInterrupted();
133
+        });
134
+
135
+        $scanner->listen('\OC\Files\Utils\Scanner', 'StorageNotAvailable', function (StorageNotAvailableException $e) use ($output) {
136
+            $output->writeln('Error while scanning, storage not available (' . $e->getMessage() . ')', OutputInterface::VERBOSITY_VERBOSE);
137
+        });
138
+
139
+        $scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function ($path) use ($output) {
140
+            $this->checkScanWarning($path, $output);
141
+        });
142
+
143
+        $scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) {
144
+            $this->checkScanWarning($path, $output);
145
+        });
146
+
147
+        try {
148
+            if ($backgroundScan) {
149
+                $scanner->backgroundScan($path);
150
+            } else {
151
+                $scanner->scan($path, $recursive, $homeOnly ? [$this, 'filterHomeMount'] : null);
152
+            }
153
+        } catch (ForbiddenException $e) {
154
+            $output->writeln("<error>Home storage for user $user not writable</error>");
155
+            $output->writeln('Make sure you\'re running the scan command only as the user the web server runs as');
156
+        } catch (InterruptedException $e) {
157
+            # exit the function if ctrl-c has been pressed
158
+            $output->writeln('Interrupted by user');
159
+        } catch (NotFoundException $e) {
160
+            $output->writeln('<error>Path not found: ' . $e->getMessage() . '</error>');
161
+        } catch (\Exception $e) {
162
+            $output->writeln('<error>Exception during scan: ' . $e->getMessage() . '</error>');
163
+            $output->writeln('<error>' . $e->getTraceAsString() . '</error>');
164
+        }
165
+    }
166
+
167
+    public function filterHomeMount(IMountPoint $mountPoint) {
168
+        // any mountpoint inside '/$user/files/'
169
+        return substr_count($mountPoint->getMountPoint(), '/') <= 3;
170
+    }
171
+
172
+    protected function execute(InputInterface $input, OutputInterface $output) {
173
+        $inputPath = $input->getOption('path');
174
+        if ($inputPath) {
175
+            $inputPath = '/' . trim($inputPath, '/');
176
+            list(, $user,) = explode('/', $inputPath, 3);
177
+            $users = [$user];
178
+        } else if ($input->getOption('all')) {
179
+            $users = $this->userManager->search('');
180
+        } else {
181
+            $users = $input->getArgument('user_id');
182
+        }
183
+
184
+        # restrict the verbosity level to VERBOSITY_VERBOSE
185
+        if ($output->getVerbosity() > OutputInterface::VERBOSITY_VERBOSE) {
186
+            $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
187
+        }
188
+
189
+        # check quantity of users to be process and show it on the command line
190
+        $users_total = count($users);
191
+        if ($users_total === 0) {
192
+            $output->writeln('<error>Please specify the user id to scan, --all to scan for all users or --path=...</error>');
193
+            return;
194
+        }
195
+
196
+        $this->initTools();
197
+
198
+        $user_count = 0;
199
+        foreach ($users as $user) {
200
+            if (is_object($user)) {
201
+                $user = $user->getUID();
202
+            }
203
+            $path = $inputPath ? $inputPath : '/' . $user;
204
+            ++$user_count;
205
+            if ($this->userManager->userExists($user)) {
206
+                $output->writeln("Starting scan for user $user_count out of $users_total ($user)");
207
+                $this->scanFiles($user, $path, $output, $input->getOption('unscanned'), !$input->getOption('shallow'), $input->getOption('home-only'));
208
+                $output->writeln('', OutputInterface::VERBOSITY_VERBOSE);
209
+            } else {
210
+                $output->writeln("<error>Unknown user $user_count $user</error>");
211
+                $output->writeln('', OutputInterface::VERBOSITY_VERBOSE);
212
+            }
213
+
214
+            try {
215
+                $this->abortIfInterrupted();
216
+            } catch (InterruptedException $e) {
217
+                break;
218
+            }
219
+        }
220
+
221
+        $this->presentStats($output);
222
+    }
223
+
224
+    /**
225
+     * Initialises some useful tools for the Command
226
+     */
227
+    protected function initTools() {
228
+        // Start the timer
229
+        $this->execTime = -microtime(true);
230
+        // Convert PHP errors to exceptions
231
+        set_error_handler([$this, 'exceptionErrorHandler'], E_ALL);
232
+    }
233
+
234
+    /**
235
+     * Processes PHP errors as exceptions in order to be able to keep track of problems
236
+     *
237
+     * @see https://secure.php.net/manual/en/function.set-error-handler.php
238
+     *
239
+     * @param int $severity the level of the error raised
240
+     * @param string $message
241
+     * @param string $file the filename that the error was raised in
242
+     * @param int $line the line number the error was raised
243
+     *
244
+     * @throws \ErrorException
245
+     */
246
+    public function exceptionErrorHandler($severity, $message, $file, $line) {
247
+        if (!(error_reporting() & $severity)) {
248
+            // This error code is not included in error_reporting
249
+            return;
250
+        }
251
+        throw new \ErrorException($message, 0, $severity, $file, $line);
252
+    }
253
+
254
+    /**
255
+     * @param OutputInterface $output
256
+     */
257
+    protected function presentStats(OutputInterface $output) {
258
+        // Stop the timer
259
+        $this->execTime += microtime(true);
260
+
261
+        $headers = [
262
+            'Folders', 'Files', 'Elapsed time'
263
+        ];
264
+
265
+        $this->showSummary($headers, null, $output);
266
+    }
267
+
268
+    /**
269
+     * Shows a summary of operations
270
+     *
271
+     * @param string[] $headers
272
+     * @param string[] $rows
273
+     * @param OutputInterface $output
274
+     */
275
+    protected function showSummary($headers, $rows, OutputInterface $output) {
276
+        $niceDate = $this->formatExecTime();
277
+        if (!$rows) {
278
+            $rows = [
279
+                $this->foldersCounter,
280
+                $this->filesCounter,
281
+                $niceDate,
282
+            ];
283
+        }
284
+        $table = new Table($output);
285
+        $table
286
+            ->setHeaders($headers)
287
+            ->setRows([$rows]);
288
+        $table->render();
289
+    }
290
+
291
+
292
+    /**
293
+     * Formats microtime into a human readable format
294
+     *
295
+     * @return string
296
+     */
297
+    protected function formatExecTime() {
298
+        $secs = round($this->execTime);
299
+        # convert seconds into HH:MM:SS form
300
+        return sprintf('%02d:%02d:%02d', ($secs/3600), ($secs/60%60), $secs%60);
301
+    }
302
+
303
+    /**
304
+     * @return \OCP\IDBConnection
305
+     */
306
+    protected function reconnectToDatabase(OutputInterface $output) {
307
+        /** @var Connection | IDBConnection $connection */
308
+        $connection = \OC::$server->getDatabaseConnection();
309
+        try {
310
+            $connection->close();
311
+        } catch (\Exception $ex) {
312
+            $output->writeln("<info>Error while disconnecting from database: {$ex->getMessage()}</info>");
313
+        }
314
+        while (!$connection->isConnected()) {
315
+            try {
316
+                $connection->connect();
317
+            } catch (\Exception $ex) {
318
+                $output->writeln("<info>Error while re-connecting to database: {$ex->getMessage()}</info>");
319
+                sleep(60);
320
+            }
321
+        }
322
+        return $connection;
323
+    }
324 324
 
325 325
 }
Please login to merge, or discard this patch.
apps/oauth2/lib/Controller/OauthApiController.php 1 patch
Indentation   +133 added lines, -133 removed lines patch added patch discarded remove patch
@@ -44,137 +44,137 @@
 block discarded – undo
44 44
 use OCP\Security\ISecureRandom;
45 45
 
46 46
 class OauthApiController extends Controller {
47
-	/** @var AccessTokenMapper */
48
-	private $accessTokenMapper;
49
-	/** @var ClientMapper */
50
-	private $clientMapper;
51
-	/** @var ICrypto */
52
-	private $crypto;
53
-	/** @var TokenProvider */
54
-	private $tokenProvider;
55
-	/** @var ISecureRandom */
56
-	private $secureRandom;
57
-	/** @var ITimeFactory */
58
-	private $time;
59
-	/** @var Throttler */
60
-	private $throttler;
61
-
62
-	public function __construct(string $appName,
63
-								IRequest $request,
64
-								ICrypto $crypto,
65
-								AccessTokenMapper $accessTokenMapper,
66
-								ClientMapper $clientMapper,
67
-								TokenProvider $tokenProvider,
68
-								ISecureRandom $secureRandom,
69
-								ITimeFactory $time,
70
-								Throttler $throttler) {
71
-		parent::__construct($appName, $request);
72
-		$this->crypto = $crypto;
73
-		$this->accessTokenMapper = $accessTokenMapper;
74
-		$this->clientMapper = $clientMapper;
75
-		$this->tokenProvider = $tokenProvider;
76
-		$this->secureRandom = $secureRandom;
77
-		$this->time = $time;
78
-		$this->throttler = $throttler;
79
-	}
80
-
81
-	/**
82
-	 * @PublicPage
83
-	 * @NoCSRFRequired
84
-	 *
85
-	 * @param string $grant_type
86
-	 * @param string $code
87
-	 * @param string $refresh_token
88
-	 * @param string $client_id
89
-	 * @param string $client_secret
90
-	 * @return JSONResponse
91
-	 */
92
-	public function getToken($grant_type, $code, $refresh_token, $client_id, $client_secret): JSONResponse {
93
-
94
-		// We only handle two types
95
-		if ($grant_type !== 'authorization_code' && $grant_type !== 'refresh_token') {
96
-			return new JSONResponse([
97
-				'error' => 'invalid_grant',
98
-			], Http::STATUS_BAD_REQUEST);
99
-		}
100
-
101
-		// We handle the initial and refresh tokens the same way
102
-		if ($grant_type === 'refresh_token') {
103
-			$code = $refresh_token;
104
-		}
105
-
106
-		try {
107
-			$accessToken = $this->accessTokenMapper->getByCode($code);
108
-		} catch (AccessTokenNotFoundException $e) {
109
-			return new JSONResponse([
110
-				'error' => 'invalid_request',
111
-			], Http::STATUS_BAD_REQUEST);
112
-		}
113
-
114
-		try {
115
-			$client = $this->clientMapper->getByUid($accessToken->getClientId());
116
-		} catch (ClientNotFoundException $e) {
117
-			return new JSONResponse([
118
-				'error' => 'invalid_request',
119
-			], Http::STATUS_BAD_REQUEST);
120
-		}
121
-
122
-		if (isset($this->request->server['PHP_AUTH_USER'])) {
123
-			$client_id = $this->request->server['PHP_AUTH_USER'];
124
-			$client_secret = $this->request->server['PHP_AUTH_PW'];
125
-		}
126
-
127
-		// The client id and secret must match. Else we don't provide an access token!
128
-		if ($client->getClientIdentifier() !== $client_id || $client->getSecret() !== $client_secret) {
129
-			return new JSONResponse([
130
-				'error' => 'invalid_client',
131
-			], Http::STATUS_BAD_REQUEST);
132
-		}
133
-
134
-		$decryptedToken = $this->crypto->decrypt($accessToken->getEncryptedToken(), $code);
135
-
136
-		// Obtain the appToken assoicated
137
-		try {
138
-			$appToken = $this->tokenProvider->getTokenById($accessToken->getTokenId());
139
-		} catch (ExpiredTokenException $e) {
140
-			$appToken = $e->getToken();
141
-		} catch (InvalidTokenException $e) {
142
-			//We can't do anything...
143
-			$this->accessTokenMapper->delete($accessToken);
144
-			return new JSONResponse([
145
-				'error' => 'invalid_request',
146
-			], Http::STATUS_BAD_REQUEST);
147
-		}
148
-
149
-		// Rotate the apptoken (so the old one becomes invalid basically)
150
-		$newToken = $this->secureRandom->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
151
-
152
-		$appToken = $this->tokenProvider->rotate(
153
-			$appToken,
154
-			$decryptedToken,
155
-			$newToken
156
-		);
157
-
158
-		// Expiration is in 1 hour again
159
-		$appToken->setExpires($this->time->getTime() + 3600);
160
-		$this->tokenProvider->updateToken($appToken);
161
-
162
-		// Generate a new refresh token and encrypt the new apptoken in the DB
163
-		$newCode = $this->secureRandom->generate(128, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
164
-		$accessToken->setHashedCode(hash('sha512', $newCode));
165
-		$accessToken->setEncryptedToken($this->crypto->encrypt($newToken, $newCode));
166
-		$this->accessTokenMapper->update($accessToken);
167
-
168
-		$this->throttler->resetDelay($this->request->getRemoteAddress(), 'login', ['user' => $appToken->getUID()]);
169
-
170
-		return new JSONResponse(
171
-			[
172
-				'access_token' => $newToken,
173
-				'token_type' => 'Bearer',
174
-				'expires_in' => 3600,
175
-				'refresh_token' => $newCode,
176
-				'user_id' => $appToken->getUID(),
177
-			]
178
-		);
179
-	}
47
+    /** @var AccessTokenMapper */
48
+    private $accessTokenMapper;
49
+    /** @var ClientMapper */
50
+    private $clientMapper;
51
+    /** @var ICrypto */
52
+    private $crypto;
53
+    /** @var TokenProvider */
54
+    private $tokenProvider;
55
+    /** @var ISecureRandom */
56
+    private $secureRandom;
57
+    /** @var ITimeFactory */
58
+    private $time;
59
+    /** @var Throttler */
60
+    private $throttler;
61
+
62
+    public function __construct(string $appName,
63
+                                IRequest $request,
64
+                                ICrypto $crypto,
65
+                                AccessTokenMapper $accessTokenMapper,
66
+                                ClientMapper $clientMapper,
67
+                                TokenProvider $tokenProvider,
68
+                                ISecureRandom $secureRandom,
69
+                                ITimeFactory $time,
70
+                                Throttler $throttler) {
71
+        parent::__construct($appName, $request);
72
+        $this->crypto = $crypto;
73
+        $this->accessTokenMapper = $accessTokenMapper;
74
+        $this->clientMapper = $clientMapper;
75
+        $this->tokenProvider = $tokenProvider;
76
+        $this->secureRandom = $secureRandom;
77
+        $this->time = $time;
78
+        $this->throttler = $throttler;
79
+    }
80
+
81
+    /**
82
+     * @PublicPage
83
+     * @NoCSRFRequired
84
+     *
85
+     * @param string $grant_type
86
+     * @param string $code
87
+     * @param string $refresh_token
88
+     * @param string $client_id
89
+     * @param string $client_secret
90
+     * @return JSONResponse
91
+     */
92
+    public function getToken($grant_type, $code, $refresh_token, $client_id, $client_secret): JSONResponse {
93
+
94
+        // We only handle two types
95
+        if ($grant_type !== 'authorization_code' && $grant_type !== 'refresh_token') {
96
+            return new JSONResponse([
97
+                'error' => 'invalid_grant',
98
+            ], Http::STATUS_BAD_REQUEST);
99
+        }
100
+
101
+        // We handle the initial and refresh tokens the same way
102
+        if ($grant_type === 'refresh_token') {
103
+            $code = $refresh_token;
104
+        }
105
+
106
+        try {
107
+            $accessToken = $this->accessTokenMapper->getByCode($code);
108
+        } catch (AccessTokenNotFoundException $e) {
109
+            return new JSONResponse([
110
+                'error' => 'invalid_request',
111
+            ], Http::STATUS_BAD_REQUEST);
112
+        }
113
+
114
+        try {
115
+            $client = $this->clientMapper->getByUid($accessToken->getClientId());
116
+        } catch (ClientNotFoundException $e) {
117
+            return new JSONResponse([
118
+                'error' => 'invalid_request',
119
+            ], Http::STATUS_BAD_REQUEST);
120
+        }
121
+
122
+        if (isset($this->request->server['PHP_AUTH_USER'])) {
123
+            $client_id = $this->request->server['PHP_AUTH_USER'];
124
+            $client_secret = $this->request->server['PHP_AUTH_PW'];
125
+        }
126
+
127
+        // The client id and secret must match. Else we don't provide an access token!
128
+        if ($client->getClientIdentifier() !== $client_id || $client->getSecret() !== $client_secret) {
129
+            return new JSONResponse([
130
+                'error' => 'invalid_client',
131
+            ], Http::STATUS_BAD_REQUEST);
132
+        }
133
+
134
+        $decryptedToken = $this->crypto->decrypt($accessToken->getEncryptedToken(), $code);
135
+
136
+        // Obtain the appToken assoicated
137
+        try {
138
+            $appToken = $this->tokenProvider->getTokenById($accessToken->getTokenId());
139
+        } catch (ExpiredTokenException $e) {
140
+            $appToken = $e->getToken();
141
+        } catch (InvalidTokenException $e) {
142
+            //We can't do anything...
143
+            $this->accessTokenMapper->delete($accessToken);
144
+            return new JSONResponse([
145
+                'error' => 'invalid_request',
146
+            ], Http::STATUS_BAD_REQUEST);
147
+        }
148
+
149
+        // Rotate the apptoken (so the old one becomes invalid basically)
150
+        $newToken = $this->secureRandom->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
151
+
152
+        $appToken = $this->tokenProvider->rotate(
153
+            $appToken,
154
+            $decryptedToken,
155
+            $newToken
156
+        );
157
+
158
+        // Expiration is in 1 hour again
159
+        $appToken->setExpires($this->time->getTime() + 3600);
160
+        $this->tokenProvider->updateToken($appToken);
161
+
162
+        // Generate a new refresh token and encrypt the new apptoken in the DB
163
+        $newCode = $this->secureRandom->generate(128, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
164
+        $accessToken->setHashedCode(hash('sha512', $newCode));
165
+        $accessToken->setEncryptedToken($this->crypto->encrypt($newToken, $newCode));
166
+        $this->accessTokenMapper->update($accessToken);
167
+
168
+        $this->throttler->resetDelay($this->request->getRemoteAddress(), 'login', ['user' => $appToken->getUID()]);
169
+
170
+        return new JSONResponse(
171
+            [
172
+                'access_token' => $newToken,
173
+                'token_type' => 'Bearer',
174
+                'expires_in' => 3600,
175
+                'refresh_token' => $newCode,
176
+                'user_id' => $appToken->getUID(),
177
+            ]
178
+        );
179
+    }
180 180
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Migration/UUIDFixGroup.php 1 patch
Indentation   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -32,10 +32,10 @@
 block discarded – undo
32 32
 use OCP\IConfig;
33 33
 
34 34
 class UUIDFixGroup extends UUIDFix {
35
-	public function __construct(GroupMapping $mapper, LDAP $ldap, IConfig $config, Helper $helper) {
36
-		$this->mapper = $mapper;
37
-		$this->proxy = new User_Proxy($helper->getServerConfigurationPrefixes(true), $ldap, $config,
38
-			\OC::$server->getNotificationManager(), \OC::$server->getUserSession(),
39
-			\OC::$server->query('LDAPUserPluginManager'));
40
-	}
35
+    public function __construct(GroupMapping $mapper, LDAP $ldap, IConfig $config, Helper $helper) {
36
+        $this->mapper = $mapper;
37
+        $this->proxy = new User_Proxy($helper->getServerConfigurationPrefixes(true), $ldap, $config,
38
+            \OC::$server->getNotificationManager(), \OC::$server->getUserSession(),
39
+            \OC::$server->query('LDAPUserPluginManager'));
40
+    }
41 41
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/User/Manager.php 1 patch
Indentation   +200 added lines, -200 removed lines patch added patch discarded remove patch
@@ -46,232 +46,232 @@
 block discarded – undo
46 46
  * cache
47 47
  */
48 48
 class Manager {
49
-	/** @var Access */
50
-	protected $access;
49
+    /** @var Access */
50
+    protected $access;
51 51
 
52
-	/** @var IConfig */
53
-	protected $ocConfig;
52
+    /** @var IConfig */
53
+    protected $ocConfig;
54 54
 
55
-	/** @var IDBConnection */
56
-	protected $db;
55
+    /** @var IDBConnection */
56
+    protected $db;
57 57
 
58
-	/** @var IUserManager */
59
-	protected $userManager;
58
+    /** @var IUserManager */
59
+    protected $userManager;
60 60
 
61
-	/** @var INotificationManager */
62
-	protected $notificationManager;
61
+    /** @var INotificationManager */
62
+    protected $notificationManager;
63 63
 
64
-	/** @var FilesystemHelper */
65
-	protected $ocFilesystem;
64
+    /** @var FilesystemHelper */
65
+    protected $ocFilesystem;
66 66
 
67
-	/** @var LogWrapper */
68
-	protected $ocLog;
67
+    /** @var LogWrapper */
68
+    protected $ocLog;
69 69
 
70
-	/** @var Image */
71
-	protected $image;
70
+    /** @var Image */
71
+    protected $image;
72 72
 
73
-	/** @param \OCP\IAvatarManager */
74
-	protected $avatarManager;
73
+    /** @param \OCP\IAvatarManager */
74
+    protected $avatarManager;
75 75
 
76
-	/**
77
-	 * @var CappedMemoryCache $usersByDN
78
-	 */
79
-	protected $usersByDN;
80
-	/**
81
-	 * @var CappedMemoryCache $usersByUid
82
-	 */
83
-	protected $usersByUid;
76
+    /**
77
+     * @var CappedMemoryCache $usersByDN
78
+     */
79
+    protected $usersByDN;
80
+    /**
81
+     * @var CappedMemoryCache $usersByUid
82
+     */
83
+    protected $usersByUid;
84 84
 
85
-	/**
86
-	 * @param IConfig $ocConfig
87
-	 * @param \OCA\User_LDAP\FilesystemHelper $ocFilesystem object that
88
-	 * gives access to necessary functions from the OC filesystem
89
-	 * @param  \OCA\User_LDAP\LogWrapper $ocLog
90
-	 * @param IAvatarManager $avatarManager
91
-	 * @param Image $image an empty image instance
92
-	 * @param IDBConnection $db
93
-	 * @throws \Exception when the methods mentioned above do not exist
94
-	 */
95
-	public function __construct(IConfig $ocConfig,
96
-								FilesystemHelper $ocFilesystem, LogWrapper $ocLog,
97
-								IAvatarManager $avatarManager, Image $image,
98
-								IDBConnection $db, IUserManager $userManager,
99
-								INotificationManager $notificationManager) {
85
+    /**
86
+     * @param IConfig $ocConfig
87
+     * @param \OCA\User_LDAP\FilesystemHelper $ocFilesystem object that
88
+     * gives access to necessary functions from the OC filesystem
89
+     * @param  \OCA\User_LDAP\LogWrapper $ocLog
90
+     * @param IAvatarManager $avatarManager
91
+     * @param Image $image an empty image instance
92
+     * @param IDBConnection $db
93
+     * @throws \Exception when the methods mentioned above do not exist
94
+     */
95
+    public function __construct(IConfig $ocConfig,
96
+                                FilesystemHelper $ocFilesystem, LogWrapper $ocLog,
97
+                                IAvatarManager $avatarManager, Image $image,
98
+                                IDBConnection $db, IUserManager $userManager,
99
+                                INotificationManager $notificationManager) {
100 100
 
101
-		$this->ocConfig            = $ocConfig;
102
-		$this->ocFilesystem        = $ocFilesystem;
103
-		$this->ocLog               = $ocLog;
104
-		$this->avatarManager       = $avatarManager;
105
-		$this->image               = $image;
106
-		$this->db                  = $db;
107
-		$this->userManager         = $userManager;
108
-		$this->notificationManager = $notificationManager;
109
-		$this->usersByDN           = new CappedMemoryCache();
110
-		$this->usersByUid          = new CappedMemoryCache();
111
-	}
101
+        $this->ocConfig            = $ocConfig;
102
+        $this->ocFilesystem        = $ocFilesystem;
103
+        $this->ocLog               = $ocLog;
104
+        $this->avatarManager       = $avatarManager;
105
+        $this->image               = $image;
106
+        $this->db                  = $db;
107
+        $this->userManager         = $userManager;
108
+        $this->notificationManager = $notificationManager;
109
+        $this->usersByDN           = new CappedMemoryCache();
110
+        $this->usersByUid          = new CappedMemoryCache();
111
+    }
112 112
 
113
-	/**
114
-	 * Binds manager to an instance of Access.
115
-	 * It needs to be assigned first before the manager can be used.
116
-	 * @param Access
117
-	 */
118
-	public function setLdapAccess(Access $access) {
119
-		$this->access = $access;
120
-	}
113
+    /**
114
+     * Binds manager to an instance of Access.
115
+     * It needs to be assigned first before the manager can be used.
116
+     * @param Access
117
+     */
118
+    public function setLdapAccess(Access $access) {
119
+        $this->access = $access;
120
+    }
121 121
 
122
-	/**
123
-	 * @brief creates an instance of User and caches (just runtime) it in the
124
-	 * property array
125
-	 * @param string $dn the DN of the user
126
-	 * @param string $uid the internal (owncloud) username
127
-	 * @return \OCA\User_LDAP\User\User
128
-	 */
129
-	private function createAndCache($dn, $uid) {
130
-		$this->checkAccess();
131
-		$user = new User($uid, $dn, $this->access, $this->ocConfig,
132
-			$this->ocFilesystem, clone $this->image, $this->ocLog,
133
-			$this->avatarManager, $this->userManager,
134
-			$this->notificationManager);
135
-		$this->usersByDN[$dn]   = $user;
136
-		$this->usersByUid[$uid] = $user;
137
-		return $user;
138
-	}
122
+    /**
123
+     * @brief creates an instance of User and caches (just runtime) it in the
124
+     * property array
125
+     * @param string $dn the DN of the user
126
+     * @param string $uid the internal (owncloud) username
127
+     * @return \OCA\User_LDAP\User\User
128
+     */
129
+    private function createAndCache($dn, $uid) {
130
+        $this->checkAccess();
131
+        $user = new User($uid, $dn, $this->access, $this->ocConfig,
132
+            $this->ocFilesystem, clone $this->image, $this->ocLog,
133
+            $this->avatarManager, $this->userManager,
134
+            $this->notificationManager);
135
+        $this->usersByDN[$dn]   = $user;
136
+        $this->usersByUid[$uid] = $user;
137
+        return $user;
138
+    }
139 139
 
140
-	/**
141
-	 * removes a user entry from the cache
142
-	 * @param $uid
143
-	 */
144
-	public function invalidate($uid) {
145
-		if(!isset($this->usersByUid[$uid])) {
146
-			return;
147
-		}
148
-		$dn = $this->usersByUid[$uid]->getDN();
149
-		unset($this->usersByUid[$uid]);
150
-		unset($this->usersByDN[$dn]);
151
-	}
140
+    /**
141
+     * removes a user entry from the cache
142
+     * @param $uid
143
+     */
144
+    public function invalidate($uid) {
145
+        if(!isset($this->usersByUid[$uid])) {
146
+            return;
147
+        }
148
+        $dn = $this->usersByUid[$uid]->getDN();
149
+        unset($this->usersByUid[$uid]);
150
+        unset($this->usersByDN[$dn]);
151
+    }
152 152
 
153
-	/**
154
-	 * @brief checks whether the Access instance has been set
155
-	 * @throws \Exception if Access has not been set
156
-	 * @return null
157
-	 */
158
-	private function checkAccess() {
159
-		if(is_null($this->access)) {
160
-			throw new \Exception('LDAP Access instance must be set first');
161
-		}
162
-	}
153
+    /**
154
+     * @brief checks whether the Access instance has been set
155
+     * @throws \Exception if Access has not been set
156
+     * @return null
157
+     */
158
+    private function checkAccess() {
159
+        if(is_null($this->access)) {
160
+            throw new \Exception('LDAP Access instance must be set first');
161
+        }
162
+    }
163 163
 
164
-	/**
165
-	 * returns a list of attributes that will be processed further, e.g. quota,
166
-	 * email, displayname, or others.
167
-	 *
168
-	 * @param bool $minimal - optional, set to true to skip attributes with big
169
-	 * payload
170
-	 * @return string[]
171
-	 */
172
-	public function getAttributes($minimal = false) {
173
-		$baseAttributes = array_merge(Access::UUID_ATTRIBUTES, ['dn', 'uid', 'samaccountname', 'memberof']);
174
-		$attributes = [
175
-			$this->access->getConnection()->ldapExpertUUIDUserAttr,
176
-			$this->access->getConnection()->ldapQuotaAttribute,
177
-			$this->access->getConnection()->ldapEmailAttribute,
178
-			$this->access->getConnection()->ldapUserDisplayName,
179
-			$this->access->getConnection()->ldapUserDisplayName2,
180
-			$this->access->getConnection()->ldapExtStorageHomeAttribute,
181
-		];
164
+    /**
165
+     * returns a list of attributes that will be processed further, e.g. quota,
166
+     * email, displayname, or others.
167
+     *
168
+     * @param bool $minimal - optional, set to true to skip attributes with big
169
+     * payload
170
+     * @return string[]
171
+     */
172
+    public function getAttributes($minimal = false) {
173
+        $baseAttributes = array_merge(Access::UUID_ATTRIBUTES, ['dn', 'uid', 'samaccountname', 'memberof']);
174
+        $attributes = [
175
+            $this->access->getConnection()->ldapExpertUUIDUserAttr,
176
+            $this->access->getConnection()->ldapQuotaAttribute,
177
+            $this->access->getConnection()->ldapEmailAttribute,
178
+            $this->access->getConnection()->ldapUserDisplayName,
179
+            $this->access->getConnection()->ldapUserDisplayName2,
180
+            $this->access->getConnection()->ldapExtStorageHomeAttribute,
181
+        ];
182 182
 
183
-		$homeRule = $this->access->getConnection()->homeFolderNamingRule;
184
-		if(strpos($homeRule, 'attr:') === 0) {
185
-			$attributes[] = substr($homeRule, strlen('attr:'));
186
-		}
183
+        $homeRule = $this->access->getConnection()->homeFolderNamingRule;
184
+        if(strpos($homeRule, 'attr:') === 0) {
185
+            $attributes[] = substr($homeRule, strlen('attr:'));
186
+        }
187 187
 
188
-		if(!$minimal) {
189
-			// attributes that are not really important but may come with big
190
-			// payload.
191
-			$attributes = array_merge(
192
-				$attributes,
193
-				$this->access->getConnection()->resolveRule('avatar')
194
-			);
195
-		}
188
+        if(!$minimal) {
189
+            // attributes that are not really important but may come with big
190
+            // payload.
191
+            $attributes = array_merge(
192
+                $attributes,
193
+                $this->access->getConnection()->resolveRule('avatar')
194
+            );
195
+        }
196 196
 
197
-		$attributes = array_reduce($attributes,
198
-			function ($list, $attribute) {
199
-				$attribute = strtolower(trim((string)$attribute));
200
-				if(!empty($attribute) && !in_array($attribute, $list)) {
201
-					$list[] = $attribute;
202
-				}
197
+        $attributes = array_reduce($attributes,
198
+            function ($list, $attribute) {
199
+                $attribute = strtolower(trim((string)$attribute));
200
+                if(!empty($attribute) && !in_array($attribute, $list)) {
201
+                    $list[] = $attribute;
202
+                }
203 203
 
204
-				return $list;
205
-			},
206
-			$baseAttributes // hard-coded, lower-case, non-empty attributes
207
-		);
204
+                return $list;
205
+            },
206
+            $baseAttributes // hard-coded, lower-case, non-empty attributes
207
+        );
208 208
 
209
-		return $attributes;
210
-	}
209
+        return $attributes;
210
+    }
211 211
 
212
-	/**
213
-	 * Checks whether the specified user is marked as deleted
214
-	 * @param string $id the Nextcloud user name
215
-	 * @return bool
216
-	 */
217
-	public function isDeletedUser($id) {
218
-		$isDeleted = $this->ocConfig->getUserValue(
219
-			$id, 'user_ldap', 'isDeleted', 0);
220
-		return (int)$isDeleted === 1;
221
-	}
212
+    /**
213
+     * Checks whether the specified user is marked as deleted
214
+     * @param string $id the Nextcloud user name
215
+     * @return bool
216
+     */
217
+    public function isDeletedUser($id) {
218
+        $isDeleted = $this->ocConfig->getUserValue(
219
+            $id, 'user_ldap', 'isDeleted', 0);
220
+        return (int)$isDeleted === 1;
221
+    }
222 222
 
223
-	/**
224
-	 * creates and returns an instance of OfflineUser for the specified user
225
-	 * @param string $id
226
-	 * @return \OCA\User_LDAP\User\OfflineUser
227
-	 */
228
-	public function getDeletedUser($id) {
229
-		return new OfflineUser(
230
-			$id,
231
-			$this->ocConfig,
232
-			$this->db,
233
-			$this->access->getUserMapper());
234
-	}
223
+    /**
224
+     * creates and returns an instance of OfflineUser for the specified user
225
+     * @param string $id
226
+     * @return \OCA\User_LDAP\User\OfflineUser
227
+     */
228
+    public function getDeletedUser($id) {
229
+        return new OfflineUser(
230
+            $id,
231
+            $this->ocConfig,
232
+            $this->db,
233
+            $this->access->getUserMapper());
234
+    }
235 235
 
236
-	/**
237
-	 * @brief returns a User object by it's Nextcloud username
238
-	 * @param string $id the DN or username of the user
239
-	 * @return \OCA\User_LDAP\User\User|\OCA\User_LDAP\User\OfflineUser|null
240
-	 */
241
-	protected function createInstancyByUserName($id) {
242
-		//most likely a uid. Check whether it is a deleted user
243
-		if($this->isDeletedUser($id)) {
244
-			return $this->getDeletedUser($id);
245
-		}
246
-		$dn = $this->access->username2dn($id);
247
-		if($dn !== false) {
248
-			return $this->createAndCache($dn, $id);
249
-		}
250
-		return null;
251
-	}
236
+    /**
237
+     * @brief returns a User object by it's Nextcloud username
238
+     * @param string $id the DN or username of the user
239
+     * @return \OCA\User_LDAP\User\User|\OCA\User_LDAP\User\OfflineUser|null
240
+     */
241
+    protected function createInstancyByUserName($id) {
242
+        //most likely a uid. Check whether it is a deleted user
243
+        if($this->isDeletedUser($id)) {
244
+            return $this->getDeletedUser($id);
245
+        }
246
+        $dn = $this->access->username2dn($id);
247
+        if($dn !== false) {
248
+            return $this->createAndCache($dn, $id);
249
+        }
250
+        return null;
251
+    }
252 252
 
253
-	/**
254
-	 * @brief returns a User object by it's DN or Nextcloud username
255
-	 * @param string $id the DN or username of the user
256
-	 * @return \OCA\User_LDAP\User\User|\OCA\User_LDAP\User\OfflineUser|null
257
-	 * @throws \Exception when connection could not be established
258
-	 */
259
-	public function get($id) {
260
-		$this->checkAccess();
261
-		if(isset($this->usersByDN[$id])) {
262
-			return $this->usersByDN[$id];
263
-		} else if(isset($this->usersByUid[$id])) {
264
-			return $this->usersByUid[$id];
265
-		}
253
+    /**
254
+     * @brief returns a User object by it's DN or Nextcloud username
255
+     * @param string $id the DN or username of the user
256
+     * @return \OCA\User_LDAP\User\User|\OCA\User_LDAP\User\OfflineUser|null
257
+     * @throws \Exception when connection could not be established
258
+     */
259
+    public function get($id) {
260
+        $this->checkAccess();
261
+        if(isset($this->usersByDN[$id])) {
262
+            return $this->usersByDN[$id];
263
+        } else if(isset($this->usersByUid[$id])) {
264
+            return $this->usersByUid[$id];
265
+        }
266 266
 
267
-		if($this->access->stringResemblesDN($id)) {
268
-			$uid = $this->access->dn2username($id);
269
-			if($uid !== false) {
270
-				return $this->createAndCache($id, $uid);
271
-			}
272
-		}
267
+        if($this->access->stringResemblesDN($id)) {
268
+            $uid = $this->access->dn2username($id);
269
+            if($uid !== false) {
270
+                return $this->createAndCache($id, $uid);
271
+            }
272
+        }
273 273
 
274
-		return $this->createInstancyByUserName($id);
275
-	}
274
+        return $this->createInstancyByUserName($id);
275
+    }
276 276
 
277 277
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/User/User.php 1 patch
Indentation   +741 added lines, -741 removed lines patch added patch discarded remove patch
@@ -51,745 +51,745 @@
 block discarded – undo
51 51
  * represents an LDAP user, gets and holds user-specific information from LDAP
52 52
  */
53 53
 class User {
54
-	/**
55
-	 * @var Access
56
-	 */
57
-	protected $access;
58
-	/**
59
-	 * @var Connection
60
-	 */
61
-	protected $connection;
62
-	/**
63
-	 * @var IConfig
64
-	 */
65
-	protected $config;
66
-	/**
67
-	 * @var FilesystemHelper
68
-	 */
69
-	protected $fs;
70
-	/**
71
-	 * @var Image
72
-	 */
73
-	protected $image;
74
-	/**
75
-	 * @var LogWrapper
76
-	 */
77
-	protected $log;
78
-	/**
79
-	 * @var IAvatarManager
80
-	 */
81
-	protected $avatarManager;
82
-	/**
83
-	 * @var IUserManager
84
-	 */
85
-	protected $userManager;
86
-	/**
87
-	 * @var INotificationManager
88
-	 */
89
-	protected $notificationManager;
90
-	/**
91
-	 * @var string
92
-	 */
93
-	protected $dn;
94
-	/**
95
-	 * @var string
96
-	 */
97
-	protected $uid;
98
-	/**
99
-	 * @var string[]
100
-	 */
101
-	protected $refreshedFeatures = [];
102
-	/**
103
-	 * @var string
104
-	 */
105
-	protected $avatarImage;
106
-
107
-	/**
108
-	 * DB config keys for user preferences
109
-	 */
110
-	const USER_PREFKEY_FIRSTLOGIN  = 'firstLoginAccomplished';
111
-	const USER_PREFKEY_LASTREFRESH = 'lastFeatureRefresh';
112
-
113
-	/**
114
-	 * @brief constructor, make sure the subclasses call this one!
115
-	 * @param string $username the internal username
116
-	 * @param string $dn the LDAP DN
117
-	 * @param Access $access
118
-	 * @param IConfig $config
119
-	 * @param FilesystemHelper $fs
120
-	 * @param Image $image any empty instance
121
-	 * @param LogWrapper $log
122
-	 * @param IAvatarManager $avatarManager
123
-	 * @param IUserManager $userManager
124
-	 * @param INotificationManager $notificationManager
125
-	 */
126
-	public function __construct($username, $dn, Access $access,
127
-		IConfig $config, FilesystemHelper $fs, Image $image,
128
-		LogWrapper $log, IAvatarManager $avatarManager, IUserManager $userManager,
129
-		INotificationManager $notificationManager) {
130
-
131
-		if ($username === null) {
132
-			$log->log("uid for '$dn' must not be null!", ILogger::ERROR);
133
-			throw new \InvalidArgumentException('uid must not be null!');
134
-		} else if ($username === '') {
135
-			$log->log("uid for '$dn' must not be an empty string", ILogger::ERROR);
136
-			throw new \InvalidArgumentException('uid must not be an empty string!');
137
-		}
138
-
139
-		$this->access              = $access;
140
-		$this->connection          = $access->getConnection();
141
-		$this->config              = $config;
142
-		$this->fs                  = $fs;
143
-		$this->dn                  = $dn;
144
-		$this->uid                 = $username;
145
-		$this->image               = $image;
146
-		$this->log                 = $log;
147
-		$this->avatarManager       = $avatarManager;
148
-		$this->userManager         = $userManager;
149
-		$this->notificationManager = $notificationManager;
150
-
151
-		\OCP\Util::connectHook('OC_User', 'post_login', $this, 'handlePasswordExpiry');
152
-	}
153
-
154
-	/**
155
-	 * @brief updates properties like email, quota or avatar provided by LDAP
156
-	 * @return null
157
-	 */
158
-	public function update() {
159
-		if(is_null($this->dn)) {
160
-			return null;
161
-		}
162
-
163
-		$hasLoggedIn = $this->config->getUserValue($this->uid, 'user_ldap',
164
-				self::USER_PREFKEY_FIRSTLOGIN, 0);
165
-
166
-		if($this->needsRefresh()) {
167
-			$this->updateEmail();
168
-			$this->updateQuota();
169
-			if($hasLoggedIn !== 0) {
170
-				//we do not need to try it, when the user has not been logged in
171
-				//before, because the file system will not be ready.
172
-				$this->updateAvatar();
173
-				//in order to get an avatar as soon as possible, mark the user
174
-				//as refreshed only when updating the avatar did happen
175
-				$this->markRefreshTime();
176
-			}
177
-		}
178
-	}
179
-
180
-	/**
181
-	 * marks a user as deleted
182
-	 *
183
-	 * @throws \OCP\PreConditionNotMetException
184
-	 */
185
-	public function markUser() {
186
-		$curValue = $this->config->getUserValue($this->getUsername(), 'user_ldap', 'isDeleted', '0');
187
-		if($curValue === '1') {
188
-			// the user is already marked, do not write to DB again
189
-			return;
190
-		}
191
-		$this->config->setUserValue($this->getUsername(), 'user_ldap', 'isDeleted', '1');
192
-		$this->config->setUserValue($this->getUsername(), 'user_ldap', 'foundDeleted', (string)time());
193
-	}
194
-
195
-	/**
196
-	 * processes results from LDAP for attributes as returned by getAttributesToRead()
197
-	 * @param array $ldapEntry the user entry as retrieved from LDAP
198
-	 */
199
-	public function processAttributes($ldapEntry) {
200
-		$this->markRefreshTime();
201
-		//Quota
202
-		$attr = strtolower($this->connection->ldapQuotaAttribute);
203
-		if(isset($ldapEntry[$attr])) {
204
-			$this->updateQuota($ldapEntry[$attr][0]);
205
-		} else {
206
-			if ($this->connection->ldapQuotaDefault !== '') {
207
-				$this->updateQuota();
208
-			}
209
-		}
210
-		unset($attr);
211
-
212
-		//displayName
213
-		$displayName = $displayName2 = '';
214
-		$attr = strtolower($this->connection->ldapUserDisplayName);
215
-		if(isset($ldapEntry[$attr])) {
216
-			$displayName = (string)$ldapEntry[$attr][0];
217
-		}
218
-		$attr = strtolower($this->connection->ldapUserDisplayName2);
219
-		if(isset($ldapEntry[$attr])) {
220
-			$displayName2 = (string)$ldapEntry[$attr][0];
221
-		}
222
-		if ($displayName !== '') {
223
-			$this->composeAndStoreDisplayName($displayName, $displayName2);
224
-			$this->access->cacheUserDisplayName(
225
-				$this->getUsername(),
226
-				$displayName,
227
-				$displayName2
228
-			);
229
-		}
230
-		unset($attr);
231
-
232
-		//Email
233
-		//email must be stored after displayname, because it would cause a user
234
-		//change event that will trigger fetching the display name again
235
-		$attr = strtolower($this->connection->ldapEmailAttribute);
236
-		if(isset($ldapEntry[$attr])) {
237
-			$this->updateEmail($ldapEntry[$attr][0]);
238
-		}
239
-		unset($attr);
240
-
241
-		// LDAP Username, needed for s2s sharing
242
-		if(isset($ldapEntry['uid'])) {
243
-			$this->storeLDAPUserName($ldapEntry['uid'][0]);
244
-		} else if(isset($ldapEntry['samaccountname'])) {
245
-			$this->storeLDAPUserName($ldapEntry['samaccountname'][0]);
246
-		}
247
-
248
-		//homePath
249
-		if(strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) {
250
-			$attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:')));
251
-			if(isset($ldapEntry[$attr])) {
252
-				$this->access->cacheUserHome(
253
-					$this->getUsername(), $this->getHomePath($ldapEntry[$attr][0]));
254
-			}
255
-		}
256
-
257
-		//memberOf groups
258
-		$cacheKey = 'getMemberOf'.$this->getUsername();
259
-		$groups = false;
260
-		if(isset($ldapEntry['memberof'])) {
261
-			$groups = $ldapEntry['memberof'];
262
-		}
263
-		$this->connection->writeToCache($cacheKey, $groups);
264
-
265
-		//external storage var
266
-		$attr = strtolower($this->connection->ldapExtStorageHomeAttribute);
267
-		if(isset($ldapEntry[$attr])) {
268
-			$this->updateExtStorageHome($ldapEntry[$attr][0]);
269
-		}
270
-		unset($attr);
271
-
272
-		//Avatar
273
-		/** @var Connection $connection */
274
-		$connection = $this->access->getConnection();
275
-		$attributes = $connection->resolveRule('avatar');
276
-		foreach ($attributes as $attribute)  {
277
-			if(isset($ldapEntry[$attribute])) {
278
-				$this->avatarImage = $ldapEntry[$attribute][0];
279
-				// the call to the method that saves the avatar in the file
280
-				// system must be postponed after the login. It is to ensure
281
-				// external mounts are mounted properly (e.g. with login
282
-				// credentials from the session).
283
-				\OCP\Util::connectHook('OC_User', 'post_login', $this, 'updateAvatarPostLogin');
284
-				break;
285
-			}
286
-		}
287
-	}
288
-
289
-	/**
290
-	 * @brief returns the LDAP DN of the user
291
-	 * @return string
292
-	 */
293
-	public function getDN() {
294
-		return $this->dn;
295
-	}
296
-
297
-	/**
298
-	 * @brief returns the Nextcloud internal username of the user
299
-	 * @return string
300
-	 */
301
-	public function getUsername() {
302
-		return $this->uid;
303
-	}
304
-
305
-	/**
306
-	 * returns the home directory of the user if specified by LDAP settings
307
-	 * @param string $valueFromLDAP
308
-	 * @return bool|string
309
-	 * @throws \Exception
310
-	 */
311
-	public function getHomePath($valueFromLDAP = null) {
312
-		$path = (string)$valueFromLDAP;
313
-		$attr = null;
314
-
315
-		if (is_null($valueFromLDAP)
316
-		   && strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0
317
-		   && $this->access->connection->homeFolderNamingRule !== 'attr:')
318
-		{
319
-			$attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:'));
320
-			$homedir = $this->access->readAttribute(
321
-				$this->access->username2dn($this->getUsername()), $attr);
322
-			if ($homedir && isset($homedir[0])) {
323
-				$path = $homedir[0];
324
-			}
325
-		}
326
-
327
-		if ($path !== '') {
328
-			//if attribute's value is an absolute path take this, otherwise append it to data dir
329
-			//check for / at the beginning or pattern c:\ resp. c:/
330
-			if('/' !== $path[0]
331
-			   && !(3 < strlen($path) && ctype_alpha($path[0])
332
-				   && $path[1] === ':' && ('\\' === $path[2] || '/' === $path[2]))
333
-			) {
334
-				$path = $this->config->getSystemValue('datadirectory',
335
-						\OC::$SERVERROOT.'/data') . '/' . $path;
336
-			}
337
-			//we need it to store it in the DB as well in case a user gets
338
-			//deleted so we can clean up afterwards
339
-			$this->config->setUserValue(
340
-				$this->getUsername(), 'user_ldap', 'homePath', $path
341
-			);
342
-			return $path;
343
-		}
344
-
345
-		if(!is_null($attr)
346
-			&& $this->config->getAppValue('user_ldap', 'enforce_home_folder_naming_rule', true)
347
-		) {
348
-			// a naming rule attribute is defined, but it doesn't exist for that LDAP user
349
-			throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: ' . $this->getUsername());
350
-		}
351
-
352
-		//false will apply default behaviour as defined and done by OC_User
353
-		$this->config->setUserValue($this->getUsername(), 'user_ldap', 'homePath', '');
354
-		return false;
355
-	}
356
-
357
-	public function getMemberOfGroups() {
358
-		$cacheKey = 'getMemberOf'.$this->getUsername();
359
-		$memberOfGroups = $this->connection->getFromCache($cacheKey);
360
-		if(!is_null($memberOfGroups)) {
361
-			return $memberOfGroups;
362
-		}
363
-		$groupDNs = $this->access->readAttribute($this->getDN(), 'memberOf');
364
-		$this->connection->writeToCache($cacheKey, $groupDNs);
365
-		return $groupDNs;
366
-	}
367
-
368
-	/**
369
-	 * @brief reads the image from LDAP that shall be used as Avatar
370
-	 * @return string data (provided by LDAP) | false
371
-	 */
372
-	public function getAvatarImage() {
373
-		if(!is_null($this->avatarImage)) {
374
-			return $this->avatarImage;
375
-		}
376
-
377
-		$this->avatarImage = false;
378
-		/** @var Connection $connection */
379
-		$connection = $this->access->getConnection();
380
-		$attributes = $connection->resolveRule('avatar');
381
-		foreach($attributes as $attribute) {
382
-			$result = $this->access->readAttribute($this->dn, $attribute);
383
-			if($result !== false && is_array($result) && isset($result[0])) {
384
-				$this->avatarImage = $result[0];
385
-				break;
386
-			}
387
-		}
388
-
389
-		return $this->avatarImage;
390
-	}
391
-
392
-	/**
393
-	 * @brief marks the user as having logged in at least once
394
-	 * @return null
395
-	 */
396
-	public function markLogin() {
397
-		$this->config->setUserValue(
398
-			$this->uid, 'user_ldap', self::USER_PREFKEY_FIRSTLOGIN, 1);
399
-	}
400
-
401
-	/**
402
-	 * @brief marks the time when user features like email have been updated
403
-	 * @return null
404
-	 */
405
-	public function markRefreshTime() {
406
-		$this->config->setUserValue(
407
-			$this->uid, 'user_ldap', self::USER_PREFKEY_LASTREFRESH, time());
408
-	}
409
-
410
-	/**
411
-	 * @brief checks whether user features needs to be updated again by
412
-	 * comparing the difference of time of the last refresh to now with the
413
-	 * desired interval
414
-	 * @return bool
415
-	 */
416
-	private function needsRefresh() {
417
-		$lastChecked = $this->config->getUserValue($this->uid, 'user_ldap',
418
-			self::USER_PREFKEY_LASTREFRESH, 0);
419
-
420
-		if((time() - (int)$lastChecked) < (int)$this->config->getAppValue('user_ldap', 'updateAttributesInterval', 86400)) {
421
-			return false;
422
-		}
423
-		return  true;
424
-	}
425
-
426
-	/**
427
-	 * Stores a key-value pair in relation to this user
428
-	 *
429
-	 * @param string $key
430
-	 * @param string $value
431
-	 */
432
-	private function store($key, $value) {
433
-		$this->config->setUserValue($this->uid, 'user_ldap', $key, $value);
434
-	}
435
-
436
-	/**
437
-	 * Composes the display name and stores it in the database. The final
438
-	 * display name is returned.
439
-	 *
440
-	 * @param string $displayName
441
-	 * @param string $displayName2
442
-	 * @return string the effective display name
443
-	 */
444
-	public function composeAndStoreDisplayName($displayName, $displayName2 = '') {
445
-		$displayName2 = (string)$displayName2;
446
-		if($displayName2 !== '') {
447
-			$displayName .= ' (' . $displayName2 . ')';
448
-		}
449
-		$oldName = $this->config->getUserValue($this->uid, 'user_ldap', 'displayName', null);
450
-		if ($oldName !== $displayName)  {
451
-			$this->store('displayName', $displayName);
452
-			$user = $this->userManager->get($this->getUsername());
453
-			if (!empty($oldName) && $user instanceof \OC\User\User) {
454
-				// if it was empty, it would be a new record, not a change emitting the trigger could
455
-				// potentially cause a UniqueConstraintViolationException, depending on some factors.
456
-				$user->triggerChange('displayName', $displayName, $oldName);
457
-			}
458
-		}
459
-		return $displayName;
460
-	}
461
-
462
-	/**
463
-	 * Stores the LDAP Username in the Database
464
-	 * @param string $userName
465
-	 */
466
-	public function storeLDAPUserName($userName) {
467
-		$this->store('uid', $userName);
468
-	}
469
-
470
-	/**
471
-	 * @brief checks whether an update method specified by feature was run
472
-	 * already. If not, it will marked like this, because it is expected that
473
-	 * the method will be run, when false is returned.
474
-	 * @param string $feature email | quota | avatar (can be extended)
475
-	 * @return bool
476
-	 */
477
-	private function wasRefreshed($feature) {
478
-		if(isset($this->refreshedFeatures[$feature])) {
479
-			return true;
480
-		}
481
-		$this->refreshedFeatures[$feature] = 1;
482
-		return false;
483
-	}
484
-
485
-	/**
486
-	 * fetches the email from LDAP and stores it as Nextcloud user value
487
-	 * @param string $valueFromLDAP if known, to save an LDAP read request
488
-	 * @return null
489
-	 */
490
-	public function updateEmail($valueFromLDAP = null) {
491
-		if($this->wasRefreshed('email')) {
492
-			return;
493
-		}
494
-		$email = (string)$valueFromLDAP;
495
-		if(is_null($valueFromLDAP)) {
496
-			$emailAttribute = $this->connection->ldapEmailAttribute;
497
-			if ($emailAttribute !== '') {
498
-				$aEmail = $this->access->readAttribute($this->dn, $emailAttribute);
499
-				if(is_array($aEmail) && (count($aEmail) > 0)) {
500
-					$email = (string)$aEmail[0];
501
-				}
502
-			}
503
-		}
504
-		if ($email !== '') {
505
-			$user = $this->userManager->get($this->uid);
506
-			if (!is_null($user)) {
507
-				$currentEmail = (string)$user->getEMailAddress();
508
-				if ($currentEmail !== $email) {
509
-					$user->setEMailAddress($email);
510
-				}
511
-			}
512
-		}
513
-	}
514
-
515
-	/**
516
-	 * Overall process goes as follow:
517
-	 * 1. fetch the quota from LDAP and check if it's parseable with the "verifyQuotaValue" function
518
-	 * 2. if the value can't be fetched, is empty or not parseable, use the default LDAP quota
519
-	 * 3. if the default LDAP quota can't be parsed, use the Nextcloud's default quota (use 'default')
520
-	 * 4. check if the target user exists and set the quota for the user.
521
-	 *
522
-	 * In order to improve performance and prevent an unwanted extra LDAP call, the $valueFromLDAP
523
-	 * parameter can be passed with the value of the attribute. This value will be considered as the
524
-	 * quota for the user coming from the LDAP server (step 1 of the process) It can be useful to
525
-	 * fetch all the user's attributes in one call and use the fetched values in this function.
526
-	 * The expected value for that parameter is a string describing the quota for the user. Valid
527
-	 * values are 'none' (unlimited), 'default' (the Nextcloud's default quota), '1234' (quota in
528
-	 * bytes), '1234 MB' (quota in MB - check the \OC_Helper::computerFileSize method for more info)
529
-	 *
530
-	 * fetches the quota from LDAP and stores it as Nextcloud user value
531
-	 * @param string $valueFromLDAP the quota attribute's value can be passed,
532
-	 * to save the readAttribute request
533
-	 * @return null
534
-	 */
535
-	public function updateQuota($valueFromLDAP = null) {
536
-		if($this->wasRefreshed('quota')) {
537
-			return;
538
-		}
539
-
540
-		$quotaAttribute = $this->connection->ldapQuotaAttribute;
541
-		$defaultQuota = $this->connection->ldapQuotaDefault;
542
-		if($quotaAttribute === '' && $defaultQuota === '') {
543
-			return;
544
-		}
545
-
546
-		$quota = false;
547
-		if(is_null($valueFromLDAP) && $quotaAttribute !== '') {
548
-			$aQuota = $this->access->readAttribute($this->dn, $quotaAttribute);
549
-			if($aQuota && (count($aQuota) > 0) && $this->verifyQuotaValue($aQuota[0])) {
550
-				$quota = $aQuota[0];
551
-			} else if(is_array($aQuota) && isset($aQuota[0])) {
552
-				$this->log->log('no suitable LDAP quota found for user ' . $this->uid . ': [' . $aQuota[0] . ']', ILogger::DEBUG);
553
-			}
554
-		} else if ($this->verifyQuotaValue($valueFromLDAP)) {
555
-			$quota = $valueFromLDAP;
556
-		} else {
557
-			$this->log->log('no suitable LDAP quota found for user ' . $this->uid . ': [' . $valueFromLDAP . ']', ILogger::DEBUG);
558
-		}
559
-
560
-		if ($quota === false && $this->verifyQuotaValue($defaultQuota)) {
561
-			// quota not found using the LDAP attribute (or not parseable). Try the default quota
562
-			$quota = $defaultQuota;
563
-		} else if($quota === false) {
564
-			$this->log->log('no suitable default quota found for user ' . $this->uid . ': [' . $defaultQuota . ']', ILogger::DEBUG);
565
-			return;
566
-		}
567
-
568
-		$targetUser = $this->userManager->get($this->uid);
569
-		if ($targetUser instanceof IUser) {
570
-			$targetUser->setQuota($quota);
571
-		} else {
572
-			$this->log->log('trying to set a quota for user ' . $this->uid . ' but the user is missing', ILogger::INFO);
573
-		}
574
-	}
575
-
576
-	private function verifyQuotaValue($quotaValue) {
577
-		return $quotaValue === 'none' || $quotaValue === 'default' || \OC_Helper::computerFileSize($quotaValue) !== false;
578
-	}
579
-
580
-	/**
581
-	 * called by a post_login hook to save the avatar picture
582
-	 *
583
-	 * @param array $params
584
-	 */
585
-	public function updateAvatarPostLogin($params) {
586
-		if(isset($params['uid']) && $params['uid'] === $this->getUsername()) {
587
-			$this->updateAvatar();
588
-		}
589
-	}
590
-
591
-	/**
592
-	 * @brief attempts to get an image from LDAP and sets it as Nextcloud avatar
593
-	 * @return bool
594
-	 */
595
-	public function updateAvatar($force = false) {
596
-		if(!$force && $this->wasRefreshed('avatar')) {
597
-			return false;
598
-		}
599
-		$avatarImage = $this->getAvatarImage();
600
-		if($avatarImage === false) {
601
-			//not set, nothing left to do;
602
-			return false;
603
-		}
604
-
605
-		if(!$this->image->loadFromBase64(base64_encode($avatarImage))) {
606
-			return false;
607
-		}
608
-
609
-		// use the checksum before modifications
610
-		$checksum = md5($this->image->data());
611
-
612
-		if($checksum === $this->config->getUserValue($this->uid, 'user_ldap', 'lastAvatarChecksum', '')) {
613
-			return true;
614
-		}
615
-
616
-		$isSet = $this->setOwnCloudAvatar();
617
-
618
-		if($isSet) {
619
-			// save checksum only after successful setting
620
-			$this->config->setUserValue($this->uid, 'user_ldap', 'lastAvatarChecksum', $checksum);
621
-		}
622
-
623
-		return $isSet;
624
-	}
625
-
626
-	/**
627
-	 * @brief sets an image as Nextcloud avatar
628
-	 * @return bool
629
-	 */
630
-	private function setOwnCloudAvatar() {
631
-		if(!$this->image->valid()) {
632
-			$this->log->log('avatar image data from LDAP invalid for '.$this->dn, ILogger::ERROR);
633
-			return false;
634
-		}
635
-
636
-
637
-		//make sure it is a square and not bigger than 128x128
638
-		$size = min([$this->image->width(), $this->image->height(), 128]);
639
-		if(!$this->image->centerCrop($size)) {
640
-			$this->log->log('croping image for avatar failed for '.$this->dn, ILogger::ERROR);
641
-			return false;
642
-		}
643
-
644
-		if(!$this->fs->isLoaded()) {
645
-			$this->fs->setup($this->uid);
646
-		}
647
-
648
-		try {
649
-			$avatar = $this->avatarManager->getAvatar($this->uid);
650
-			$avatar->set($this->image);
651
-			return true;
652
-		} catch (\Exception $e) {
653
-			\OC::$server->getLogger()->logException($e, [
654
-				'message' => 'Could not set avatar for ' . $this->dn,
655
-				'level' => ILogger::INFO,
656
-				'app' => 'user_ldap',
657
-			]);
658
-		}
659
-		return false;
660
-	}
661
-
662
-	/**
663
-	 * @throws AttributeNotSet
664
-	 * @throws \OC\ServerNotAvailableException
665
-	 * @throws \OCP\PreConditionNotMetException
666
-	 */
667
-	public function getExtStorageHome():string {
668
-		$value = $this->config->getUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', '');
669
-		if ($value !== '') {
670
-			return $value;
671
-		}
672
-
673
-		$value = $this->updateExtStorageHome();
674
-		if ($value !== '') {
675
-			return $value;
676
-		}
677
-
678
-		throw new AttributeNotSet(sprintf(
679
-			'external home storage attribute yield no value for %s', $this->getUsername()
680
-		));
681
-	}
682
-
683
-	/**
684
-	 * @throws \OCP\PreConditionNotMetException
685
-	 * @throws \OC\ServerNotAvailableException
686
-	 */
687
-	public function updateExtStorageHome(string $valueFromLDAP = null):string {
688
-		if ($valueFromLDAP === null) {
689
-			$extHomeValues = $this->access->readAttribute($this->getDN(), $this->connection->ldapExtStorageHomeAttribute);
690
-		} else {
691
-			$extHomeValues = [$valueFromLDAP];
692
-		}
693
-		if ($extHomeValues && isset($extHomeValues[0])) {
694
-			$extHome = $extHomeValues[0];
695
-			$this->config->setUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', $extHome);
696
-			return $extHome;
697
-		} else {
698
-			$this->config->deleteUserValue($this->getUsername(), 'user_ldap', 'extStorageHome');
699
-			return '';
700
-		}
701
-	}
702
-
703
-	/**
704
-	 * called by a post_login hook to handle password expiry
705
-	 *
706
-	 * @param array $params
707
-	 */
708
-	public function handlePasswordExpiry($params) {
709
-		$ppolicyDN = $this->connection->ldapDefaultPPolicyDN;
710
-		if (empty($ppolicyDN) || ((int)$this->connection->turnOnPasswordChange !== 1)) {
711
-			return;//password expiry handling disabled
712
-		}
713
-		$uid = $params['uid'];
714
-		if (isset($uid) && $uid === $this->getUsername()) {
715
-			//retrieve relevant user attributes
716
-			$result = $this->access->search('objectclass=*', [$this->dn], ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
717
-
718
-			if (array_key_exists('pwdpolicysubentry', $result[0])) {
719
-				$pwdPolicySubentry = $result[0]['pwdpolicysubentry'];
720
-				if ($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)){
721
-					$ppolicyDN = $pwdPolicySubentry[0];//custom ppolicy DN
722
-				}
723
-			}
724
-
725
-			$pwdGraceUseTime = array_key_exists('pwdgraceusetime', $result[0]) ? $result[0]['pwdgraceusetime'] : [];
726
-			$pwdReset = array_key_exists('pwdreset', $result[0]) ? $result[0]['pwdreset'] : [];
727
-			$pwdChangedTime = array_key_exists('pwdchangedtime', $result[0]) ? $result[0]['pwdchangedtime'] : [];
728
-
729
-			//retrieve relevant password policy attributes
730
-			$cacheKey = 'ppolicyAttributes' . $ppolicyDN;
731
-			$result = $this->connection->getFromCache($cacheKey);
732
-			if(is_null($result)) {
733
-				$result = $this->access->search('objectclass=*', [$ppolicyDN], ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']);
734
-				$this->connection->writeToCache($cacheKey, $result);
735
-			}
736
-
737
-			$pwdGraceAuthNLimit = array_key_exists('pwdgraceauthnlimit', $result[0]) ? $result[0]['pwdgraceauthnlimit'] : [];
738
-			$pwdMaxAge = array_key_exists('pwdmaxage', $result[0]) ? $result[0]['pwdmaxage'] : [];
739
-			$pwdExpireWarning = array_key_exists('pwdexpirewarning', $result[0]) ? $result[0]['pwdexpirewarning'] : [];
740
-
741
-			//handle grace login
742
-			if (!empty($pwdGraceUseTime)) { //was this a grace login?
743
-				if (!empty($pwdGraceAuthNLimit)
744
-					&& count($pwdGraceUseTime) < (int)$pwdGraceAuthNLimit[0]) { //at least one more grace login available?
745
-					$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
746
-					header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
747
-					'user_ldap.renewPassword.showRenewPasswordForm', ['user' => $uid]));
748
-				} else { //no more grace login available
749
-					header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
750
-					'user_ldap.renewPassword.showLoginFormInvalidPassword', ['user' => $uid]));
751
-				}
752
-				exit();
753
-			}
754
-			//handle pwdReset attribute
755
-			if (!empty($pwdReset) && $pwdReset[0] === 'TRUE') { //user must change his password
756
-				$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
757
-				header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
758
-				'user_ldap.renewPassword.showRenewPasswordForm', ['user' => $uid]));
759
-				exit();
760
-			}
761
-			//handle password expiry warning
762
-			if (!empty($pwdChangedTime)) {
763
-				if (!empty($pwdMaxAge)
764
-					&& !empty($pwdExpireWarning)) {
765
-					$pwdMaxAgeInt = (int)$pwdMaxAge[0];
766
-					$pwdExpireWarningInt = (int)$pwdExpireWarning[0];
767
-					if ($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0){
768
-						$pwdChangedTimeDt = \DateTime::createFromFormat('YmdHisZ', $pwdChangedTime[0]);
769
-						$pwdChangedTimeDt->add(new \DateInterval('PT'.$pwdMaxAgeInt.'S'));
770
-						$currentDateTime = new \DateTime();
771
-						$secondsToExpiry = $pwdChangedTimeDt->getTimestamp() - $currentDateTime->getTimestamp();
772
-						if ($secondsToExpiry <= $pwdExpireWarningInt) {
773
-							//remove last password expiry warning if any
774
-							$notification = $this->notificationManager->createNotification();
775
-							$notification->setApp('user_ldap')
776
-								->setUser($uid)
777
-								->setObject('pwd_exp_warn', $uid)
778
-							;
779
-							$this->notificationManager->markProcessed($notification);
780
-							//create new password expiry warning
781
-							$notification = $this->notificationManager->createNotification();
782
-							$notification->setApp('user_ldap')
783
-								->setUser($uid)
784
-								->setDateTime($currentDateTime)
785
-								->setObject('pwd_exp_warn', $uid)
786
-								->setSubject('pwd_exp_warn_days', [(int) ceil($secondsToExpiry / 60 / 60 / 24)])
787
-							;
788
-							$this->notificationManager->notify($notification);
789
-						}
790
-					}
791
-				}
792
-			}
793
-		}
794
-	}
54
+    /**
55
+     * @var Access
56
+     */
57
+    protected $access;
58
+    /**
59
+     * @var Connection
60
+     */
61
+    protected $connection;
62
+    /**
63
+     * @var IConfig
64
+     */
65
+    protected $config;
66
+    /**
67
+     * @var FilesystemHelper
68
+     */
69
+    protected $fs;
70
+    /**
71
+     * @var Image
72
+     */
73
+    protected $image;
74
+    /**
75
+     * @var LogWrapper
76
+     */
77
+    protected $log;
78
+    /**
79
+     * @var IAvatarManager
80
+     */
81
+    protected $avatarManager;
82
+    /**
83
+     * @var IUserManager
84
+     */
85
+    protected $userManager;
86
+    /**
87
+     * @var INotificationManager
88
+     */
89
+    protected $notificationManager;
90
+    /**
91
+     * @var string
92
+     */
93
+    protected $dn;
94
+    /**
95
+     * @var string
96
+     */
97
+    protected $uid;
98
+    /**
99
+     * @var string[]
100
+     */
101
+    protected $refreshedFeatures = [];
102
+    /**
103
+     * @var string
104
+     */
105
+    protected $avatarImage;
106
+
107
+    /**
108
+     * DB config keys for user preferences
109
+     */
110
+    const USER_PREFKEY_FIRSTLOGIN  = 'firstLoginAccomplished';
111
+    const USER_PREFKEY_LASTREFRESH = 'lastFeatureRefresh';
112
+
113
+    /**
114
+     * @brief constructor, make sure the subclasses call this one!
115
+     * @param string $username the internal username
116
+     * @param string $dn the LDAP DN
117
+     * @param Access $access
118
+     * @param IConfig $config
119
+     * @param FilesystemHelper $fs
120
+     * @param Image $image any empty instance
121
+     * @param LogWrapper $log
122
+     * @param IAvatarManager $avatarManager
123
+     * @param IUserManager $userManager
124
+     * @param INotificationManager $notificationManager
125
+     */
126
+    public function __construct($username, $dn, Access $access,
127
+        IConfig $config, FilesystemHelper $fs, Image $image,
128
+        LogWrapper $log, IAvatarManager $avatarManager, IUserManager $userManager,
129
+        INotificationManager $notificationManager) {
130
+
131
+        if ($username === null) {
132
+            $log->log("uid for '$dn' must not be null!", ILogger::ERROR);
133
+            throw new \InvalidArgumentException('uid must not be null!');
134
+        } else if ($username === '') {
135
+            $log->log("uid for '$dn' must not be an empty string", ILogger::ERROR);
136
+            throw new \InvalidArgumentException('uid must not be an empty string!');
137
+        }
138
+
139
+        $this->access              = $access;
140
+        $this->connection          = $access->getConnection();
141
+        $this->config              = $config;
142
+        $this->fs                  = $fs;
143
+        $this->dn                  = $dn;
144
+        $this->uid                 = $username;
145
+        $this->image               = $image;
146
+        $this->log                 = $log;
147
+        $this->avatarManager       = $avatarManager;
148
+        $this->userManager         = $userManager;
149
+        $this->notificationManager = $notificationManager;
150
+
151
+        \OCP\Util::connectHook('OC_User', 'post_login', $this, 'handlePasswordExpiry');
152
+    }
153
+
154
+    /**
155
+     * @brief updates properties like email, quota or avatar provided by LDAP
156
+     * @return null
157
+     */
158
+    public function update() {
159
+        if(is_null($this->dn)) {
160
+            return null;
161
+        }
162
+
163
+        $hasLoggedIn = $this->config->getUserValue($this->uid, 'user_ldap',
164
+                self::USER_PREFKEY_FIRSTLOGIN, 0);
165
+
166
+        if($this->needsRefresh()) {
167
+            $this->updateEmail();
168
+            $this->updateQuota();
169
+            if($hasLoggedIn !== 0) {
170
+                //we do not need to try it, when the user has not been logged in
171
+                //before, because the file system will not be ready.
172
+                $this->updateAvatar();
173
+                //in order to get an avatar as soon as possible, mark the user
174
+                //as refreshed only when updating the avatar did happen
175
+                $this->markRefreshTime();
176
+            }
177
+        }
178
+    }
179
+
180
+    /**
181
+     * marks a user as deleted
182
+     *
183
+     * @throws \OCP\PreConditionNotMetException
184
+     */
185
+    public function markUser() {
186
+        $curValue = $this->config->getUserValue($this->getUsername(), 'user_ldap', 'isDeleted', '0');
187
+        if($curValue === '1') {
188
+            // the user is already marked, do not write to DB again
189
+            return;
190
+        }
191
+        $this->config->setUserValue($this->getUsername(), 'user_ldap', 'isDeleted', '1');
192
+        $this->config->setUserValue($this->getUsername(), 'user_ldap', 'foundDeleted', (string)time());
193
+    }
194
+
195
+    /**
196
+     * processes results from LDAP for attributes as returned by getAttributesToRead()
197
+     * @param array $ldapEntry the user entry as retrieved from LDAP
198
+     */
199
+    public function processAttributes($ldapEntry) {
200
+        $this->markRefreshTime();
201
+        //Quota
202
+        $attr = strtolower($this->connection->ldapQuotaAttribute);
203
+        if(isset($ldapEntry[$attr])) {
204
+            $this->updateQuota($ldapEntry[$attr][0]);
205
+        } else {
206
+            if ($this->connection->ldapQuotaDefault !== '') {
207
+                $this->updateQuota();
208
+            }
209
+        }
210
+        unset($attr);
211
+
212
+        //displayName
213
+        $displayName = $displayName2 = '';
214
+        $attr = strtolower($this->connection->ldapUserDisplayName);
215
+        if(isset($ldapEntry[$attr])) {
216
+            $displayName = (string)$ldapEntry[$attr][0];
217
+        }
218
+        $attr = strtolower($this->connection->ldapUserDisplayName2);
219
+        if(isset($ldapEntry[$attr])) {
220
+            $displayName2 = (string)$ldapEntry[$attr][0];
221
+        }
222
+        if ($displayName !== '') {
223
+            $this->composeAndStoreDisplayName($displayName, $displayName2);
224
+            $this->access->cacheUserDisplayName(
225
+                $this->getUsername(),
226
+                $displayName,
227
+                $displayName2
228
+            );
229
+        }
230
+        unset($attr);
231
+
232
+        //Email
233
+        //email must be stored after displayname, because it would cause a user
234
+        //change event that will trigger fetching the display name again
235
+        $attr = strtolower($this->connection->ldapEmailAttribute);
236
+        if(isset($ldapEntry[$attr])) {
237
+            $this->updateEmail($ldapEntry[$attr][0]);
238
+        }
239
+        unset($attr);
240
+
241
+        // LDAP Username, needed for s2s sharing
242
+        if(isset($ldapEntry['uid'])) {
243
+            $this->storeLDAPUserName($ldapEntry['uid'][0]);
244
+        } else if(isset($ldapEntry['samaccountname'])) {
245
+            $this->storeLDAPUserName($ldapEntry['samaccountname'][0]);
246
+        }
247
+
248
+        //homePath
249
+        if(strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) {
250
+            $attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:')));
251
+            if(isset($ldapEntry[$attr])) {
252
+                $this->access->cacheUserHome(
253
+                    $this->getUsername(), $this->getHomePath($ldapEntry[$attr][0]));
254
+            }
255
+        }
256
+
257
+        //memberOf groups
258
+        $cacheKey = 'getMemberOf'.$this->getUsername();
259
+        $groups = false;
260
+        if(isset($ldapEntry['memberof'])) {
261
+            $groups = $ldapEntry['memberof'];
262
+        }
263
+        $this->connection->writeToCache($cacheKey, $groups);
264
+
265
+        //external storage var
266
+        $attr = strtolower($this->connection->ldapExtStorageHomeAttribute);
267
+        if(isset($ldapEntry[$attr])) {
268
+            $this->updateExtStorageHome($ldapEntry[$attr][0]);
269
+        }
270
+        unset($attr);
271
+
272
+        //Avatar
273
+        /** @var Connection $connection */
274
+        $connection = $this->access->getConnection();
275
+        $attributes = $connection->resolveRule('avatar');
276
+        foreach ($attributes as $attribute)  {
277
+            if(isset($ldapEntry[$attribute])) {
278
+                $this->avatarImage = $ldapEntry[$attribute][0];
279
+                // the call to the method that saves the avatar in the file
280
+                // system must be postponed after the login. It is to ensure
281
+                // external mounts are mounted properly (e.g. with login
282
+                // credentials from the session).
283
+                \OCP\Util::connectHook('OC_User', 'post_login', $this, 'updateAvatarPostLogin');
284
+                break;
285
+            }
286
+        }
287
+    }
288
+
289
+    /**
290
+     * @brief returns the LDAP DN of the user
291
+     * @return string
292
+     */
293
+    public function getDN() {
294
+        return $this->dn;
295
+    }
296
+
297
+    /**
298
+     * @brief returns the Nextcloud internal username of the user
299
+     * @return string
300
+     */
301
+    public function getUsername() {
302
+        return $this->uid;
303
+    }
304
+
305
+    /**
306
+     * returns the home directory of the user if specified by LDAP settings
307
+     * @param string $valueFromLDAP
308
+     * @return bool|string
309
+     * @throws \Exception
310
+     */
311
+    public function getHomePath($valueFromLDAP = null) {
312
+        $path = (string)$valueFromLDAP;
313
+        $attr = null;
314
+
315
+        if (is_null($valueFromLDAP)
316
+           && strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0
317
+           && $this->access->connection->homeFolderNamingRule !== 'attr:')
318
+        {
319
+            $attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:'));
320
+            $homedir = $this->access->readAttribute(
321
+                $this->access->username2dn($this->getUsername()), $attr);
322
+            if ($homedir && isset($homedir[0])) {
323
+                $path = $homedir[0];
324
+            }
325
+        }
326
+
327
+        if ($path !== '') {
328
+            //if attribute's value is an absolute path take this, otherwise append it to data dir
329
+            //check for / at the beginning or pattern c:\ resp. c:/
330
+            if('/' !== $path[0]
331
+               && !(3 < strlen($path) && ctype_alpha($path[0])
332
+                   && $path[1] === ':' && ('\\' === $path[2] || '/' === $path[2]))
333
+            ) {
334
+                $path = $this->config->getSystemValue('datadirectory',
335
+                        \OC::$SERVERROOT.'/data') . '/' . $path;
336
+            }
337
+            //we need it to store it in the DB as well in case a user gets
338
+            //deleted so we can clean up afterwards
339
+            $this->config->setUserValue(
340
+                $this->getUsername(), 'user_ldap', 'homePath', $path
341
+            );
342
+            return $path;
343
+        }
344
+
345
+        if(!is_null($attr)
346
+            && $this->config->getAppValue('user_ldap', 'enforce_home_folder_naming_rule', true)
347
+        ) {
348
+            // a naming rule attribute is defined, but it doesn't exist for that LDAP user
349
+            throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: ' . $this->getUsername());
350
+        }
351
+
352
+        //false will apply default behaviour as defined and done by OC_User
353
+        $this->config->setUserValue($this->getUsername(), 'user_ldap', 'homePath', '');
354
+        return false;
355
+    }
356
+
357
+    public function getMemberOfGroups() {
358
+        $cacheKey = 'getMemberOf'.$this->getUsername();
359
+        $memberOfGroups = $this->connection->getFromCache($cacheKey);
360
+        if(!is_null($memberOfGroups)) {
361
+            return $memberOfGroups;
362
+        }
363
+        $groupDNs = $this->access->readAttribute($this->getDN(), 'memberOf');
364
+        $this->connection->writeToCache($cacheKey, $groupDNs);
365
+        return $groupDNs;
366
+    }
367
+
368
+    /**
369
+     * @brief reads the image from LDAP that shall be used as Avatar
370
+     * @return string data (provided by LDAP) | false
371
+     */
372
+    public function getAvatarImage() {
373
+        if(!is_null($this->avatarImage)) {
374
+            return $this->avatarImage;
375
+        }
376
+
377
+        $this->avatarImage = false;
378
+        /** @var Connection $connection */
379
+        $connection = $this->access->getConnection();
380
+        $attributes = $connection->resolveRule('avatar');
381
+        foreach($attributes as $attribute) {
382
+            $result = $this->access->readAttribute($this->dn, $attribute);
383
+            if($result !== false && is_array($result) && isset($result[0])) {
384
+                $this->avatarImage = $result[0];
385
+                break;
386
+            }
387
+        }
388
+
389
+        return $this->avatarImage;
390
+    }
391
+
392
+    /**
393
+     * @brief marks the user as having logged in at least once
394
+     * @return null
395
+     */
396
+    public function markLogin() {
397
+        $this->config->setUserValue(
398
+            $this->uid, 'user_ldap', self::USER_PREFKEY_FIRSTLOGIN, 1);
399
+    }
400
+
401
+    /**
402
+     * @brief marks the time when user features like email have been updated
403
+     * @return null
404
+     */
405
+    public function markRefreshTime() {
406
+        $this->config->setUserValue(
407
+            $this->uid, 'user_ldap', self::USER_PREFKEY_LASTREFRESH, time());
408
+    }
409
+
410
+    /**
411
+     * @brief checks whether user features needs to be updated again by
412
+     * comparing the difference of time of the last refresh to now with the
413
+     * desired interval
414
+     * @return bool
415
+     */
416
+    private function needsRefresh() {
417
+        $lastChecked = $this->config->getUserValue($this->uid, 'user_ldap',
418
+            self::USER_PREFKEY_LASTREFRESH, 0);
419
+
420
+        if((time() - (int)$lastChecked) < (int)$this->config->getAppValue('user_ldap', 'updateAttributesInterval', 86400)) {
421
+            return false;
422
+        }
423
+        return  true;
424
+    }
425
+
426
+    /**
427
+     * Stores a key-value pair in relation to this user
428
+     *
429
+     * @param string $key
430
+     * @param string $value
431
+     */
432
+    private function store($key, $value) {
433
+        $this->config->setUserValue($this->uid, 'user_ldap', $key, $value);
434
+    }
435
+
436
+    /**
437
+     * Composes the display name and stores it in the database. The final
438
+     * display name is returned.
439
+     *
440
+     * @param string $displayName
441
+     * @param string $displayName2
442
+     * @return string the effective display name
443
+     */
444
+    public function composeAndStoreDisplayName($displayName, $displayName2 = '') {
445
+        $displayName2 = (string)$displayName2;
446
+        if($displayName2 !== '') {
447
+            $displayName .= ' (' . $displayName2 . ')';
448
+        }
449
+        $oldName = $this->config->getUserValue($this->uid, 'user_ldap', 'displayName', null);
450
+        if ($oldName !== $displayName)  {
451
+            $this->store('displayName', $displayName);
452
+            $user = $this->userManager->get($this->getUsername());
453
+            if (!empty($oldName) && $user instanceof \OC\User\User) {
454
+                // if it was empty, it would be a new record, not a change emitting the trigger could
455
+                // potentially cause a UniqueConstraintViolationException, depending on some factors.
456
+                $user->triggerChange('displayName', $displayName, $oldName);
457
+            }
458
+        }
459
+        return $displayName;
460
+    }
461
+
462
+    /**
463
+     * Stores the LDAP Username in the Database
464
+     * @param string $userName
465
+     */
466
+    public function storeLDAPUserName($userName) {
467
+        $this->store('uid', $userName);
468
+    }
469
+
470
+    /**
471
+     * @brief checks whether an update method specified by feature was run
472
+     * already. If not, it will marked like this, because it is expected that
473
+     * the method will be run, when false is returned.
474
+     * @param string $feature email | quota | avatar (can be extended)
475
+     * @return bool
476
+     */
477
+    private function wasRefreshed($feature) {
478
+        if(isset($this->refreshedFeatures[$feature])) {
479
+            return true;
480
+        }
481
+        $this->refreshedFeatures[$feature] = 1;
482
+        return false;
483
+    }
484
+
485
+    /**
486
+     * fetches the email from LDAP and stores it as Nextcloud user value
487
+     * @param string $valueFromLDAP if known, to save an LDAP read request
488
+     * @return null
489
+     */
490
+    public function updateEmail($valueFromLDAP = null) {
491
+        if($this->wasRefreshed('email')) {
492
+            return;
493
+        }
494
+        $email = (string)$valueFromLDAP;
495
+        if(is_null($valueFromLDAP)) {
496
+            $emailAttribute = $this->connection->ldapEmailAttribute;
497
+            if ($emailAttribute !== '') {
498
+                $aEmail = $this->access->readAttribute($this->dn, $emailAttribute);
499
+                if(is_array($aEmail) && (count($aEmail) > 0)) {
500
+                    $email = (string)$aEmail[0];
501
+                }
502
+            }
503
+        }
504
+        if ($email !== '') {
505
+            $user = $this->userManager->get($this->uid);
506
+            if (!is_null($user)) {
507
+                $currentEmail = (string)$user->getEMailAddress();
508
+                if ($currentEmail !== $email) {
509
+                    $user->setEMailAddress($email);
510
+                }
511
+            }
512
+        }
513
+    }
514
+
515
+    /**
516
+     * Overall process goes as follow:
517
+     * 1. fetch the quota from LDAP and check if it's parseable with the "verifyQuotaValue" function
518
+     * 2. if the value can't be fetched, is empty or not parseable, use the default LDAP quota
519
+     * 3. if the default LDAP quota can't be parsed, use the Nextcloud's default quota (use 'default')
520
+     * 4. check if the target user exists and set the quota for the user.
521
+     *
522
+     * In order to improve performance and prevent an unwanted extra LDAP call, the $valueFromLDAP
523
+     * parameter can be passed with the value of the attribute. This value will be considered as the
524
+     * quota for the user coming from the LDAP server (step 1 of the process) It can be useful to
525
+     * fetch all the user's attributes in one call and use the fetched values in this function.
526
+     * The expected value for that parameter is a string describing the quota for the user. Valid
527
+     * values are 'none' (unlimited), 'default' (the Nextcloud's default quota), '1234' (quota in
528
+     * bytes), '1234 MB' (quota in MB - check the \OC_Helper::computerFileSize method for more info)
529
+     *
530
+     * fetches the quota from LDAP and stores it as Nextcloud user value
531
+     * @param string $valueFromLDAP the quota attribute's value can be passed,
532
+     * to save the readAttribute request
533
+     * @return null
534
+     */
535
+    public function updateQuota($valueFromLDAP = null) {
536
+        if($this->wasRefreshed('quota')) {
537
+            return;
538
+        }
539
+
540
+        $quotaAttribute = $this->connection->ldapQuotaAttribute;
541
+        $defaultQuota = $this->connection->ldapQuotaDefault;
542
+        if($quotaAttribute === '' && $defaultQuota === '') {
543
+            return;
544
+        }
545
+
546
+        $quota = false;
547
+        if(is_null($valueFromLDAP) && $quotaAttribute !== '') {
548
+            $aQuota = $this->access->readAttribute($this->dn, $quotaAttribute);
549
+            if($aQuota && (count($aQuota) > 0) && $this->verifyQuotaValue($aQuota[0])) {
550
+                $quota = $aQuota[0];
551
+            } else if(is_array($aQuota) && isset($aQuota[0])) {
552
+                $this->log->log('no suitable LDAP quota found for user ' . $this->uid . ': [' . $aQuota[0] . ']', ILogger::DEBUG);
553
+            }
554
+        } else if ($this->verifyQuotaValue($valueFromLDAP)) {
555
+            $quota = $valueFromLDAP;
556
+        } else {
557
+            $this->log->log('no suitable LDAP quota found for user ' . $this->uid . ': [' . $valueFromLDAP . ']', ILogger::DEBUG);
558
+        }
559
+
560
+        if ($quota === false && $this->verifyQuotaValue($defaultQuota)) {
561
+            // quota not found using the LDAP attribute (or not parseable). Try the default quota
562
+            $quota = $defaultQuota;
563
+        } else if($quota === false) {
564
+            $this->log->log('no suitable default quota found for user ' . $this->uid . ': [' . $defaultQuota . ']', ILogger::DEBUG);
565
+            return;
566
+        }
567
+
568
+        $targetUser = $this->userManager->get($this->uid);
569
+        if ($targetUser instanceof IUser) {
570
+            $targetUser->setQuota($quota);
571
+        } else {
572
+            $this->log->log('trying to set a quota for user ' . $this->uid . ' but the user is missing', ILogger::INFO);
573
+        }
574
+    }
575
+
576
+    private function verifyQuotaValue($quotaValue) {
577
+        return $quotaValue === 'none' || $quotaValue === 'default' || \OC_Helper::computerFileSize($quotaValue) !== false;
578
+    }
579
+
580
+    /**
581
+     * called by a post_login hook to save the avatar picture
582
+     *
583
+     * @param array $params
584
+     */
585
+    public function updateAvatarPostLogin($params) {
586
+        if(isset($params['uid']) && $params['uid'] === $this->getUsername()) {
587
+            $this->updateAvatar();
588
+        }
589
+    }
590
+
591
+    /**
592
+     * @brief attempts to get an image from LDAP and sets it as Nextcloud avatar
593
+     * @return bool
594
+     */
595
+    public function updateAvatar($force = false) {
596
+        if(!$force && $this->wasRefreshed('avatar')) {
597
+            return false;
598
+        }
599
+        $avatarImage = $this->getAvatarImage();
600
+        if($avatarImage === false) {
601
+            //not set, nothing left to do;
602
+            return false;
603
+        }
604
+
605
+        if(!$this->image->loadFromBase64(base64_encode($avatarImage))) {
606
+            return false;
607
+        }
608
+
609
+        // use the checksum before modifications
610
+        $checksum = md5($this->image->data());
611
+
612
+        if($checksum === $this->config->getUserValue($this->uid, 'user_ldap', 'lastAvatarChecksum', '')) {
613
+            return true;
614
+        }
615
+
616
+        $isSet = $this->setOwnCloudAvatar();
617
+
618
+        if($isSet) {
619
+            // save checksum only after successful setting
620
+            $this->config->setUserValue($this->uid, 'user_ldap', 'lastAvatarChecksum', $checksum);
621
+        }
622
+
623
+        return $isSet;
624
+    }
625
+
626
+    /**
627
+     * @brief sets an image as Nextcloud avatar
628
+     * @return bool
629
+     */
630
+    private function setOwnCloudAvatar() {
631
+        if(!$this->image->valid()) {
632
+            $this->log->log('avatar image data from LDAP invalid for '.$this->dn, ILogger::ERROR);
633
+            return false;
634
+        }
635
+
636
+
637
+        //make sure it is a square and not bigger than 128x128
638
+        $size = min([$this->image->width(), $this->image->height(), 128]);
639
+        if(!$this->image->centerCrop($size)) {
640
+            $this->log->log('croping image for avatar failed for '.$this->dn, ILogger::ERROR);
641
+            return false;
642
+        }
643
+
644
+        if(!$this->fs->isLoaded()) {
645
+            $this->fs->setup($this->uid);
646
+        }
647
+
648
+        try {
649
+            $avatar = $this->avatarManager->getAvatar($this->uid);
650
+            $avatar->set($this->image);
651
+            return true;
652
+        } catch (\Exception $e) {
653
+            \OC::$server->getLogger()->logException($e, [
654
+                'message' => 'Could not set avatar for ' . $this->dn,
655
+                'level' => ILogger::INFO,
656
+                'app' => 'user_ldap',
657
+            ]);
658
+        }
659
+        return false;
660
+    }
661
+
662
+    /**
663
+     * @throws AttributeNotSet
664
+     * @throws \OC\ServerNotAvailableException
665
+     * @throws \OCP\PreConditionNotMetException
666
+     */
667
+    public function getExtStorageHome():string {
668
+        $value = $this->config->getUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', '');
669
+        if ($value !== '') {
670
+            return $value;
671
+        }
672
+
673
+        $value = $this->updateExtStorageHome();
674
+        if ($value !== '') {
675
+            return $value;
676
+        }
677
+
678
+        throw new AttributeNotSet(sprintf(
679
+            'external home storage attribute yield no value for %s', $this->getUsername()
680
+        ));
681
+    }
682
+
683
+    /**
684
+     * @throws \OCP\PreConditionNotMetException
685
+     * @throws \OC\ServerNotAvailableException
686
+     */
687
+    public function updateExtStorageHome(string $valueFromLDAP = null):string {
688
+        if ($valueFromLDAP === null) {
689
+            $extHomeValues = $this->access->readAttribute($this->getDN(), $this->connection->ldapExtStorageHomeAttribute);
690
+        } else {
691
+            $extHomeValues = [$valueFromLDAP];
692
+        }
693
+        if ($extHomeValues && isset($extHomeValues[0])) {
694
+            $extHome = $extHomeValues[0];
695
+            $this->config->setUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', $extHome);
696
+            return $extHome;
697
+        } else {
698
+            $this->config->deleteUserValue($this->getUsername(), 'user_ldap', 'extStorageHome');
699
+            return '';
700
+        }
701
+    }
702
+
703
+    /**
704
+     * called by a post_login hook to handle password expiry
705
+     *
706
+     * @param array $params
707
+     */
708
+    public function handlePasswordExpiry($params) {
709
+        $ppolicyDN = $this->connection->ldapDefaultPPolicyDN;
710
+        if (empty($ppolicyDN) || ((int)$this->connection->turnOnPasswordChange !== 1)) {
711
+            return;//password expiry handling disabled
712
+        }
713
+        $uid = $params['uid'];
714
+        if (isset($uid) && $uid === $this->getUsername()) {
715
+            //retrieve relevant user attributes
716
+            $result = $this->access->search('objectclass=*', [$this->dn], ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
717
+
718
+            if (array_key_exists('pwdpolicysubentry', $result[0])) {
719
+                $pwdPolicySubentry = $result[0]['pwdpolicysubentry'];
720
+                if ($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)){
721
+                    $ppolicyDN = $pwdPolicySubentry[0];//custom ppolicy DN
722
+                }
723
+            }
724
+
725
+            $pwdGraceUseTime = array_key_exists('pwdgraceusetime', $result[0]) ? $result[0]['pwdgraceusetime'] : [];
726
+            $pwdReset = array_key_exists('pwdreset', $result[0]) ? $result[0]['pwdreset'] : [];
727
+            $pwdChangedTime = array_key_exists('pwdchangedtime', $result[0]) ? $result[0]['pwdchangedtime'] : [];
728
+
729
+            //retrieve relevant password policy attributes
730
+            $cacheKey = 'ppolicyAttributes' . $ppolicyDN;
731
+            $result = $this->connection->getFromCache($cacheKey);
732
+            if(is_null($result)) {
733
+                $result = $this->access->search('objectclass=*', [$ppolicyDN], ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']);
734
+                $this->connection->writeToCache($cacheKey, $result);
735
+            }
736
+
737
+            $pwdGraceAuthNLimit = array_key_exists('pwdgraceauthnlimit', $result[0]) ? $result[0]['pwdgraceauthnlimit'] : [];
738
+            $pwdMaxAge = array_key_exists('pwdmaxage', $result[0]) ? $result[0]['pwdmaxage'] : [];
739
+            $pwdExpireWarning = array_key_exists('pwdexpirewarning', $result[0]) ? $result[0]['pwdexpirewarning'] : [];
740
+
741
+            //handle grace login
742
+            if (!empty($pwdGraceUseTime)) { //was this a grace login?
743
+                if (!empty($pwdGraceAuthNLimit)
744
+                    && count($pwdGraceUseTime) < (int)$pwdGraceAuthNLimit[0]) { //at least one more grace login available?
745
+                    $this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
746
+                    header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
747
+                    'user_ldap.renewPassword.showRenewPasswordForm', ['user' => $uid]));
748
+                } else { //no more grace login available
749
+                    header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
750
+                    'user_ldap.renewPassword.showLoginFormInvalidPassword', ['user' => $uid]));
751
+                }
752
+                exit();
753
+            }
754
+            //handle pwdReset attribute
755
+            if (!empty($pwdReset) && $pwdReset[0] === 'TRUE') { //user must change his password
756
+                $this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
757
+                header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
758
+                'user_ldap.renewPassword.showRenewPasswordForm', ['user' => $uid]));
759
+                exit();
760
+            }
761
+            //handle password expiry warning
762
+            if (!empty($pwdChangedTime)) {
763
+                if (!empty($pwdMaxAge)
764
+                    && !empty($pwdExpireWarning)) {
765
+                    $pwdMaxAgeInt = (int)$pwdMaxAge[0];
766
+                    $pwdExpireWarningInt = (int)$pwdExpireWarning[0];
767
+                    if ($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0){
768
+                        $pwdChangedTimeDt = \DateTime::createFromFormat('YmdHisZ', $pwdChangedTime[0]);
769
+                        $pwdChangedTimeDt->add(new \DateInterval('PT'.$pwdMaxAgeInt.'S'));
770
+                        $currentDateTime = new \DateTime();
771
+                        $secondsToExpiry = $pwdChangedTimeDt->getTimestamp() - $currentDateTime->getTimestamp();
772
+                        if ($secondsToExpiry <= $pwdExpireWarningInt) {
773
+                            //remove last password expiry warning if any
774
+                            $notification = $this->notificationManager->createNotification();
775
+                            $notification->setApp('user_ldap')
776
+                                ->setUser($uid)
777
+                                ->setObject('pwd_exp_warn', $uid)
778
+                            ;
779
+                            $this->notificationManager->markProcessed($notification);
780
+                            //create new password expiry warning
781
+                            $notification = $this->notificationManager->createNotification();
782
+                            $notification->setApp('user_ldap')
783
+                                ->setUser($uid)
784
+                                ->setDateTime($currentDateTime)
785
+                                ->setObject('pwd_exp_warn', $uid)
786
+                                ->setSubject('pwd_exp_warn_days', [(int) ceil($secondsToExpiry / 60 / 60 / 24)])
787
+                            ;
788
+                            $this->notificationManager->notify($notification);
789
+                        }
790
+                    }
791
+                }
792
+            }
793
+        }
794
+    }
795 795
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Helper.php 1 patch
Indentation   +265 added lines, -265 removed lines patch added patch discarded remove patch
@@ -38,126 +38,126 @@  discard block
 block discarded – undo
38 38
 
39 39
 class Helper {
40 40
 
41
-	/** @var IConfig */
42
-	private $config;
43
-
44
-	/**
45
-	 * Helper constructor.
46
-	 *
47
-	 * @param IConfig $config
48
-	 */
49
-	public function __construct(IConfig $config) {
50
-		$this->config = $config;
51
-	}
52
-
53
-	/**
54
-	 * returns prefixes for each saved LDAP/AD server configuration.
55
-	 * @param bool $activeConfigurations optional, whether only active configuration shall be
56
-	 * retrieved, defaults to false
57
-	 * @return array with a list of the available prefixes
58
-	 *
59
-	 * Configuration prefixes are used to set up configurations for n LDAP or
60
-	 * AD servers. Since configuration is stored in the database, table
61
-	 * appconfig under appid user_ldap, the common identifiers in column
62
-	 * 'configkey' have a prefix. The prefix for the very first server
63
-	 * configuration is empty.
64
-	 * Configkey Examples:
65
-	 * Server 1: ldap_login_filter
66
-	 * Server 2: s1_ldap_login_filter
67
-	 * Server 3: s2_ldap_login_filter
68
-	 *
69
-	 * The prefix needs to be passed to the constructor of Connection class,
70
-	 * except the default (first) server shall be connected to.
71
-	 *
72
-	 */
73
-	public function getServerConfigurationPrefixes($activeConfigurations = false) {
74
-		$referenceConfigkey = 'ldap_configuration_active';
75
-
76
-		$keys = $this->getServersConfig($referenceConfigkey);
77
-
78
-		$prefixes = [];
79
-		foreach ($keys as $key) {
80
-			if ($activeConfigurations && $this->config->getAppValue('user_ldap', $key, '0') !== '1') {
81
-				continue;
82
-			}
83
-
84
-			$len = strlen($key) - strlen($referenceConfigkey);
85
-			$prefixes[] = substr($key, 0, $len);
86
-		}
87
-		asort($prefixes);
88
-
89
-		return $prefixes;
90
-	}
91
-
92
-	/**
93
-	 *
94
-	 * determines the host for every configured connection
95
-	 * @return array an array with configprefix as keys
96
-	 *
97
-	 */
98
-	public function getServerConfigurationHosts() {
99
-		$referenceConfigkey = 'ldap_host';
100
-
101
-		$keys = $this->getServersConfig($referenceConfigkey);
102
-
103
-		$result = [];
104
-		foreach($keys as $key) {
105
-			$len = strlen($key) - strlen($referenceConfigkey);
106
-			$prefix = substr($key, 0, $len);
107
-			$result[$prefix] = $this->config->getAppValue('user_ldap', $key);
108
-		}
109
-
110
-		return $result;
111
-	}
112
-
113
-	/**
114
-	 * return the next available configuration prefix
115
-	 *
116
-	 * @return string
117
-	 */
118
-	public function getNextServerConfigurationPrefix() {
119
-		$serverConnections = $this->getServerConfigurationPrefixes();
120
-
121
-		if(count($serverConnections) === 0) {
122
-			return 's01';
123
-		}
124
-
125
-		sort($serverConnections);
126
-		$lastKey = array_pop($serverConnections);
127
-		$lastNumber = (int)str_replace('s', '', $lastKey);
128
-		return 's' . str_pad($lastNumber + 1, 2, '0', STR_PAD_LEFT);
129
-	}
130
-
131
-	private function getServersConfig($value) {
132
-		$regex = '/' . $value . '$/S';
133
-
134
-		$keys = $this->config->getAppKeys('user_ldap');
135
-		$result = [];
136
-		foreach ($keys as $key) {
137
-			if (preg_match($regex, $key) === 1) {
138
-				$result[] = $key;
139
-			}
140
-		}
141
-
142
-		return $result;
143
-	}
144
-
145
-	/**
146
-	 * deletes a given saved LDAP/AD server configuration.
147
-	 * @param string $prefix the configuration prefix of the config to delete
148
-	 * @return bool true on success, false otherwise
149
-	 */
150
-	public function deleteServerConfiguration($prefix) {
151
-		if(!in_array($prefix, self::getServerConfigurationPrefixes())) {
152
-			return false;
153
-		}
154
-
155
-		$saveOtherConfigurations = '';
156
-		if(empty($prefix)) {
157
-			$saveOtherConfigurations = 'AND `configkey` NOT LIKE \'s%\'';
158
-		}
159
-
160
-		$query = \OC_DB::prepare('
41
+    /** @var IConfig */
42
+    private $config;
43
+
44
+    /**
45
+     * Helper constructor.
46
+     *
47
+     * @param IConfig $config
48
+     */
49
+    public function __construct(IConfig $config) {
50
+        $this->config = $config;
51
+    }
52
+
53
+    /**
54
+     * returns prefixes for each saved LDAP/AD server configuration.
55
+     * @param bool $activeConfigurations optional, whether only active configuration shall be
56
+     * retrieved, defaults to false
57
+     * @return array with a list of the available prefixes
58
+     *
59
+     * Configuration prefixes are used to set up configurations for n LDAP or
60
+     * AD servers. Since configuration is stored in the database, table
61
+     * appconfig under appid user_ldap, the common identifiers in column
62
+     * 'configkey' have a prefix. The prefix for the very first server
63
+     * configuration is empty.
64
+     * Configkey Examples:
65
+     * Server 1: ldap_login_filter
66
+     * Server 2: s1_ldap_login_filter
67
+     * Server 3: s2_ldap_login_filter
68
+     *
69
+     * The prefix needs to be passed to the constructor of Connection class,
70
+     * except the default (first) server shall be connected to.
71
+     *
72
+     */
73
+    public function getServerConfigurationPrefixes($activeConfigurations = false) {
74
+        $referenceConfigkey = 'ldap_configuration_active';
75
+
76
+        $keys = $this->getServersConfig($referenceConfigkey);
77
+
78
+        $prefixes = [];
79
+        foreach ($keys as $key) {
80
+            if ($activeConfigurations && $this->config->getAppValue('user_ldap', $key, '0') !== '1') {
81
+                continue;
82
+            }
83
+
84
+            $len = strlen($key) - strlen($referenceConfigkey);
85
+            $prefixes[] = substr($key, 0, $len);
86
+        }
87
+        asort($prefixes);
88
+
89
+        return $prefixes;
90
+    }
91
+
92
+    /**
93
+     *
94
+     * determines the host for every configured connection
95
+     * @return array an array with configprefix as keys
96
+     *
97
+     */
98
+    public function getServerConfigurationHosts() {
99
+        $referenceConfigkey = 'ldap_host';
100
+
101
+        $keys = $this->getServersConfig($referenceConfigkey);
102
+
103
+        $result = [];
104
+        foreach($keys as $key) {
105
+            $len = strlen($key) - strlen($referenceConfigkey);
106
+            $prefix = substr($key, 0, $len);
107
+            $result[$prefix] = $this->config->getAppValue('user_ldap', $key);
108
+        }
109
+
110
+        return $result;
111
+    }
112
+
113
+    /**
114
+     * return the next available configuration prefix
115
+     *
116
+     * @return string
117
+     */
118
+    public function getNextServerConfigurationPrefix() {
119
+        $serverConnections = $this->getServerConfigurationPrefixes();
120
+
121
+        if(count($serverConnections) === 0) {
122
+            return 's01';
123
+        }
124
+
125
+        sort($serverConnections);
126
+        $lastKey = array_pop($serverConnections);
127
+        $lastNumber = (int)str_replace('s', '', $lastKey);
128
+        return 's' . str_pad($lastNumber + 1, 2, '0', STR_PAD_LEFT);
129
+    }
130
+
131
+    private function getServersConfig($value) {
132
+        $regex = '/' . $value . '$/S';
133
+
134
+        $keys = $this->config->getAppKeys('user_ldap');
135
+        $result = [];
136
+        foreach ($keys as $key) {
137
+            if (preg_match($regex, $key) === 1) {
138
+                $result[] = $key;
139
+            }
140
+        }
141
+
142
+        return $result;
143
+    }
144
+
145
+    /**
146
+     * deletes a given saved LDAP/AD server configuration.
147
+     * @param string $prefix the configuration prefix of the config to delete
148
+     * @return bool true on success, false otherwise
149
+     */
150
+    public function deleteServerConfiguration($prefix) {
151
+        if(!in_array($prefix, self::getServerConfigurationPrefixes())) {
152
+            return false;
153
+        }
154
+
155
+        $saveOtherConfigurations = '';
156
+        if(empty($prefix)) {
157
+            $saveOtherConfigurations = 'AND `configkey` NOT LIKE \'s%\'';
158
+        }
159
+
160
+        $query = \OC_DB::prepare('
161 161
 			DELETE
162 162
 			FROM `*PREFIX*appconfig`
163 163
 			WHERE `configkey` LIKE ?
@@ -165,149 +165,149 @@  discard block
 block discarded – undo
165 165
 				AND `appid` = \'user_ldap\'
166 166
 				AND `configkey` NOT IN (\'enabled\', \'installed_version\', \'types\', \'bgjUpdateGroupsLastRun\')
167 167
 		');
168
-		$delRows = $query->execute([$prefix.'%']);
169
-
170
-		if($delRows === null) {
171
-			return false;
172
-		}
173
-
174
-		if($delRows === 0) {
175
-			return false;
176
-		}
177
-
178
-		return true;
179
-	}
180
-
181
-	/**
182
-	 * checks whether there is one or more disabled LDAP configurations
183
-	 * @throws \Exception
184
-	 * @return bool
185
-	 */
186
-	public function haveDisabledConfigurations() {
187
-		$all = $this->getServerConfigurationPrefixes(false);
188
-		$active = $this->getServerConfigurationPrefixes(true);
189
-
190
-		if(!is_array($all) || !is_array($active)) {
191
-			throw new \Exception('Unexpected Return Value');
192
-		}
193
-
194
-		return count($all) !== count($active) || count($all) === 0;
195
-	}
196
-
197
-	/**
198
-	 * extracts the domain from a given URL
199
-	 * @param string $url the URL
200
-	 * @return string|false domain as string on success, false otherwise
201
-	 */
202
-	public function getDomainFromURL($url) {
203
-		$uinfo = parse_url($url);
204
-		if(!is_array($uinfo)) {
205
-			return false;
206
-		}
207
-
208
-		$domain = false;
209
-		if(isset($uinfo['host'])) {
210
-			$domain = $uinfo['host'];
211
-		} else if(isset($uinfo['path'])) {
212
-			$domain = $uinfo['path'];
213
-		}
214
-
215
-		return $domain;
216
-	}
217
-
218
-	/**
219
-	 *
220
-	 * Set the LDAPProvider in the config
221
-	 *
222
-	 */
223
-	public function setLDAPProvider() {
224
-		$current = \OC::$server->getConfig()->getSystemValue('ldapProviderFactory', null);
225
-		if(is_null($current)) {
226
-			\OC::$server->getConfig()->setSystemValue('ldapProviderFactory', LDAPProviderFactory::class);
227
-		}
228
-	}
229
-
230
-	/**
231
-	 * sanitizes a DN received from the LDAP server
232
-	 * @param array $dn the DN in question
233
-	 * @return array|string the sanitized DN
234
-	 */
235
-	public function sanitizeDN($dn) {
236
-		//treating multiple base DNs
237
-		if(is_array($dn)) {
238
-			$result = [];
239
-			foreach($dn as $singleDN) {
240
-				$result[] = $this->sanitizeDN($singleDN);
241
-			}
242
-			return $result;
243
-		}
244
-
245
-		//OID sometimes gives back DNs with whitespace after the comma
246
-		// a la "uid=foo, cn=bar, dn=..." We need to tackle this!
247
-		$dn = preg_replace('/([^\\\]),(\s+)/u', '\1,', $dn);
248
-
249
-		//make comparisons and everything work
250
-		$dn = mb_strtolower($dn, 'UTF-8');
251
-
252
-		//escape DN values according to RFC 2253 – this is already done by ldap_explode_dn
253
-		//to use the DN in search filters, \ needs to be escaped to \5c additionally
254
-		//to use them in bases, we convert them back to simple backslashes in readAttribute()
255
-		$replacements = [
256
-			'\,' => '\5c2C',
257
-			'\=' => '\5c3D',
258
-			'\+' => '\5c2B',
259
-			'\<' => '\5c3C',
260
-			'\>' => '\5c3E',
261
-			'\;' => '\5c3B',
262
-			'\"' => '\5c22',
263
-			'\#' => '\5c23',
264
-			'('  => '\28',
265
-			')'  => '\29',
266
-			'*'  => '\2A',
267
-		];
268
-		$dn = str_replace(array_keys($replacements), array_values($replacements), $dn);
269
-
270
-		return $dn;
271
-	}
272
-
273
-	/**
274
-	 * converts a stored DN so it can be used as base parameter for LDAP queries, internally we store them for usage in LDAP filters
275
-	 * @param string $dn the DN
276
-	 * @return string
277
-	 */
278
-	public function DNasBaseParameter($dn) {
279
-		return str_ireplace('\\5c', '\\', $dn);
280
-	}
281
-
282
-	/**
283
-	 * listens to a hook thrown by server2server sharing and replaces the given
284
-	 * login name by a username, if it matches an LDAP user.
285
-	 *
286
-	 * @param array $param
287
-	 * @throws \Exception
288
-	 */
289
-	public static function loginName2UserName($param) {
290
-		if(!isset($param['uid'])) {
291
-			throw new \Exception('key uid is expected to be set in $param');
292
-		}
293
-
294
-		//ain't it ironic?
295
-		$helper = new Helper(\OC::$server->getConfig());
296
-
297
-		$configPrefixes = $helper->getServerConfigurationPrefixes(true);
298
-		$ldapWrapper = new LDAP();
299
-		$ocConfig = \OC::$server->getConfig();
300
-		$notificationManager = \OC::$server->getNotificationManager();
301
-
302
-		$userSession = \OC::$server->getUserSession();
303
-		$userPluginManager = \OC::$server->query('LDAPUserPluginManager');
304
-
305
-		$userBackend  = new User_Proxy(
306
-			$configPrefixes, $ldapWrapper, $ocConfig, $notificationManager, $userSession, $userPluginManager
307
-		);
308
-		$uid = $userBackend->loginName2UserName($param['uid']);
309
-		if($uid !== false) {
310
-			$param['uid'] = $uid;
311
-		}
312
-	}
168
+        $delRows = $query->execute([$prefix.'%']);
169
+
170
+        if($delRows === null) {
171
+            return false;
172
+        }
173
+
174
+        if($delRows === 0) {
175
+            return false;
176
+        }
177
+
178
+        return true;
179
+    }
180
+
181
+    /**
182
+     * checks whether there is one or more disabled LDAP configurations
183
+     * @throws \Exception
184
+     * @return bool
185
+     */
186
+    public function haveDisabledConfigurations() {
187
+        $all = $this->getServerConfigurationPrefixes(false);
188
+        $active = $this->getServerConfigurationPrefixes(true);
189
+
190
+        if(!is_array($all) || !is_array($active)) {
191
+            throw new \Exception('Unexpected Return Value');
192
+        }
193
+
194
+        return count($all) !== count($active) || count($all) === 0;
195
+    }
196
+
197
+    /**
198
+     * extracts the domain from a given URL
199
+     * @param string $url the URL
200
+     * @return string|false domain as string on success, false otherwise
201
+     */
202
+    public function getDomainFromURL($url) {
203
+        $uinfo = parse_url($url);
204
+        if(!is_array($uinfo)) {
205
+            return false;
206
+        }
207
+
208
+        $domain = false;
209
+        if(isset($uinfo['host'])) {
210
+            $domain = $uinfo['host'];
211
+        } else if(isset($uinfo['path'])) {
212
+            $domain = $uinfo['path'];
213
+        }
214
+
215
+        return $domain;
216
+    }
217
+
218
+    /**
219
+     *
220
+     * Set the LDAPProvider in the config
221
+     *
222
+     */
223
+    public function setLDAPProvider() {
224
+        $current = \OC::$server->getConfig()->getSystemValue('ldapProviderFactory', null);
225
+        if(is_null($current)) {
226
+            \OC::$server->getConfig()->setSystemValue('ldapProviderFactory', LDAPProviderFactory::class);
227
+        }
228
+    }
229
+
230
+    /**
231
+     * sanitizes a DN received from the LDAP server
232
+     * @param array $dn the DN in question
233
+     * @return array|string the sanitized DN
234
+     */
235
+    public function sanitizeDN($dn) {
236
+        //treating multiple base DNs
237
+        if(is_array($dn)) {
238
+            $result = [];
239
+            foreach($dn as $singleDN) {
240
+                $result[] = $this->sanitizeDN($singleDN);
241
+            }
242
+            return $result;
243
+        }
244
+
245
+        //OID sometimes gives back DNs with whitespace after the comma
246
+        // a la "uid=foo, cn=bar, dn=..." We need to tackle this!
247
+        $dn = preg_replace('/([^\\\]),(\s+)/u', '\1,', $dn);
248
+
249
+        //make comparisons and everything work
250
+        $dn = mb_strtolower($dn, 'UTF-8');
251
+
252
+        //escape DN values according to RFC 2253 – this is already done by ldap_explode_dn
253
+        //to use the DN in search filters, \ needs to be escaped to \5c additionally
254
+        //to use them in bases, we convert them back to simple backslashes in readAttribute()
255
+        $replacements = [
256
+            '\,' => '\5c2C',
257
+            '\=' => '\5c3D',
258
+            '\+' => '\5c2B',
259
+            '\<' => '\5c3C',
260
+            '\>' => '\5c3E',
261
+            '\;' => '\5c3B',
262
+            '\"' => '\5c22',
263
+            '\#' => '\5c23',
264
+            '('  => '\28',
265
+            ')'  => '\29',
266
+            '*'  => '\2A',
267
+        ];
268
+        $dn = str_replace(array_keys($replacements), array_values($replacements), $dn);
269
+
270
+        return $dn;
271
+    }
272
+
273
+    /**
274
+     * converts a stored DN so it can be used as base parameter for LDAP queries, internally we store them for usage in LDAP filters
275
+     * @param string $dn the DN
276
+     * @return string
277
+     */
278
+    public function DNasBaseParameter($dn) {
279
+        return str_ireplace('\\5c', '\\', $dn);
280
+    }
281
+
282
+    /**
283
+     * listens to a hook thrown by server2server sharing and replaces the given
284
+     * login name by a username, if it matches an LDAP user.
285
+     *
286
+     * @param array $param
287
+     * @throws \Exception
288
+     */
289
+    public static function loginName2UserName($param) {
290
+        if(!isset($param['uid'])) {
291
+            throw new \Exception('key uid is expected to be set in $param');
292
+        }
293
+
294
+        //ain't it ironic?
295
+        $helper = new Helper(\OC::$server->getConfig());
296
+
297
+        $configPrefixes = $helper->getServerConfigurationPrefixes(true);
298
+        $ldapWrapper = new LDAP();
299
+        $ocConfig = \OC::$server->getConfig();
300
+        $notificationManager = \OC::$server->getNotificationManager();
301
+
302
+        $userSession = \OC::$server->getUserSession();
303
+        $userPluginManager = \OC::$server->query('LDAPUserPluginManager');
304
+
305
+        $userBackend  = new User_Proxy(
306
+            $configPrefixes, $ldapWrapper, $ocConfig, $notificationManager, $userSession, $userPluginManager
307
+        );
308
+        $uid = $userBackend->loginName2UserName($param['uid']);
309
+        if($uid !== false) {
310
+            $param['uid'] = $uid;
311
+        }
312
+    }
313 313
 }
Please login to merge, or discard this patch.