Passed
Push — master ( 62403d...0c3e2f )
by Joas
14:50 queued 14s
created
apps/dav/lib/CardDAV/CardDavBackend.php 1 patch
Indentation   +1118 added lines, -1118 removed lines patch added patch discarded remove patch
@@ -55,1122 +55,1122 @@
 block discarded – undo
55 55
 
56 56
 class CardDavBackend implements BackendInterface, SyncSupport {
57 57
 
58
-	const PERSONAL_ADDRESSBOOK_URI = 'contacts';
59
-	const PERSONAL_ADDRESSBOOK_NAME = 'Contacts';
60
-
61
-	/** @var Principal */
62
-	private $principalBackend;
63
-
64
-	/** @var string */
65
-	private $dbCardsTable = 'cards';
66
-
67
-	/** @var string */
68
-	private $dbCardsPropertiesTable = 'cards_properties';
69
-
70
-	/** @var IDBConnection */
71
-	private $db;
72
-
73
-	/** @var Backend */
74
-	private $sharingBackend;
75
-
76
-	/** @var array properties to index */
77
-	public static $indexProperties = [
78
-			'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
79
-			'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD'];
80
-
81
-	/**
82
-	 * @var string[] Map of uid => display name
83
-	 */
84
-	protected $userDisplayNames;
85
-
86
-	/** @var IUserManager */
87
-	private $userManager;
88
-
89
-	/** @var EventDispatcherInterface */
90
-	private $dispatcher;
91
-
92
-	/**
93
-	 * CardDavBackend constructor.
94
-	 *
95
-	 * @param IDBConnection $db
96
-	 * @param Principal $principalBackend
97
-	 * @param IUserManager $userManager
98
-	 * @param IGroupManager $groupManager
99
-	 * @param EventDispatcherInterface $dispatcher
100
-	 */
101
-	public function __construct(IDBConnection $db,
102
-								Principal $principalBackend,
103
-								IUserManager $userManager,
104
-								IGroupManager $groupManager,
105
-								EventDispatcherInterface $dispatcher) {
106
-		$this->db = $db;
107
-		$this->principalBackend = $principalBackend;
108
-		$this->userManager = $userManager;
109
-		$this->dispatcher = $dispatcher;
110
-		$this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'addressbook');
111
-	}
112
-
113
-	/**
114
-	 * Return the number of address books for a principal
115
-	 *
116
-	 * @param $principalUri
117
-	 * @return int
118
-	 */
119
-	public function getAddressBooksForUserCount($principalUri) {
120
-		$principalUri = $this->convertPrincipal($principalUri, true);
121
-		$query = $this->db->getQueryBuilder();
122
-		$query->select($query->func()->count('*'))
123
-			->from('addressbooks')
124
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
125
-
126
-		return (int)$query->execute()->fetchColumn();
127
-	}
128
-
129
-	/**
130
-	 * Returns the list of address books for a specific user.
131
-	 *
132
-	 * Every addressbook should have the following properties:
133
-	 *   id - an arbitrary unique id
134
-	 *   uri - the 'basename' part of the url
135
-	 *   principaluri - Same as the passed parameter
136
-	 *
137
-	 * Any additional clark-notation property may be passed besides this. Some
138
-	 * common ones are :
139
-	 *   {DAV:}displayname
140
-	 *   {urn:ietf:params:xml:ns:carddav}addressbook-description
141
-	 *   {http://calendarserver.org/ns/}getctag
142
-	 *
143
-	 * @param string $principalUri
144
-	 * @return array
145
-	 */
146
-	function getAddressBooksForUser($principalUri) {
147
-		$principalUriOriginal = $principalUri;
148
-		$principalUri = $this->convertPrincipal($principalUri, true);
149
-		$query = $this->db->getQueryBuilder();
150
-		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
151
-			->from('addressbooks')
152
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
153
-
154
-		$addressBooks = [];
155
-
156
-		$result = $query->execute();
157
-		while($row = $result->fetch()) {
158
-			$addressBooks[$row['id']] = [
159
-				'id'  => $row['id'],
160
-				'uri' => $row['uri'],
161
-				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
162
-				'{DAV:}displayname' => $row['displayname'],
163
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
164
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
165
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
166
-			];
167
-
168
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
169
-		}
170
-		$result->closeCursor();
171
-
172
-		// query for shared addressbooks
173
-		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
174
-		$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
175
-
176
-		$principals = array_map(function($principal) {
177
-			return urldecode($principal);
178
-		}, $principals);
179
-		$principals[]= $principalUri;
180
-
181
-		$query = $this->db->getQueryBuilder();
182
-		$result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
183
-			->from('dav_shares', 's')
184
-			->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
185
-			->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
186
-			->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
187
-			->setParameter('type', 'addressbook')
188
-			->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
189
-			->execute();
190
-
191
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
192
-		while($row = $result->fetch()) {
193
-			if ($row['principaluri'] === $principalUri) {
194
-				continue;
195
-			}
196
-
197
-			$readOnly = (int) $row['access'] === Backend::ACCESS_READ;
198
-			if (isset($addressBooks[$row['id']])) {
199
-				if ($readOnly) {
200
-					// New share can not have more permissions then the old one.
201
-					continue;
202
-				}
203
-				if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
204
-					$addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
205
-					// Old share is already read-write, no more permissions can be gained
206
-					continue;
207
-				}
208
-			}
209
-
210
-			list(, $name) = \Sabre\Uri\split($row['principaluri']);
211
-			$uri = $row['uri'] . '_shared_by_' . $name;
212
-			$displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
213
-
214
-			$addressBooks[$row['id']] = [
215
-				'id'  => $row['id'],
216
-				'uri' => $uri,
217
-				'principaluri' => $principalUriOriginal,
218
-				'{DAV:}displayname' => $displayName,
219
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
220
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
221
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
222
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
223
-				$readOnlyPropertyName => $readOnly,
224
-			];
225
-
226
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
227
-		}
228
-		$result->closeCursor();
229
-
230
-		return array_values($addressBooks);
231
-	}
232
-
233
-	public function getUsersOwnAddressBooks($principalUri) {
234
-		$principalUri = $this->convertPrincipal($principalUri, true);
235
-		$query = $this->db->getQueryBuilder();
236
-		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
237
-			  ->from('addressbooks')
238
-			  ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
239
-
240
-		$addressBooks = [];
241
-
242
-		$result = $query->execute();
243
-		while($row = $result->fetch()) {
244
-			$addressBooks[$row['id']] = [
245
-				'id'  => $row['id'],
246
-				'uri' => $row['uri'],
247
-				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
248
-				'{DAV:}displayname' => $row['displayname'],
249
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
250
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
251
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
252
-			];
253
-
254
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
255
-		}
256
-		$result->closeCursor();
257
-
258
-		return array_values($addressBooks);
259
-	}
260
-
261
-	private function getUserDisplayName($uid) {
262
-		if (!isset($this->userDisplayNames[$uid])) {
263
-			$user = $this->userManager->get($uid);
264
-
265
-			if ($user instanceof IUser) {
266
-				$this->userDisplayNames[$uid] = $user->getDisplayName();
267
-			} else {
268
-				$this->userDisplayNames[$uid] = $uid;
269
-			}
270
-		}
271
-
272
-		return $this->userDisplayNames[$uid];
273
-	}
274
-
275
-	/**
276
-	 * @param int $addressBookId
277
-	 */
278
-	public function getAddressBookById($addressBookId) {
279
-		$query = $this->db->getQueryBuilder();
280
-		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
281
-			->from('addressbooks')
282
-			->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
283
-			->execute();
284
-
285
-		$row = $result->fetch();
286
-		$result->closeCursor();
287
-		if ($row === false) {
288
-			return null;
289
-		}
290
-
291
-		$addressBook = [
292
-			'id'  => $row['id'],
293
-			'uri' => $row['uri'],
294
-			'principaluri' => $row['principaluri'],
295
-			'{DAV:}displayname' => $row['displayname'],
296
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
297
-			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
298
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
299
-		];
300
-
301
-		$this->addOwnerPrincipal($addressBook);
302
-
303
-		return $addressBook;
304
-	}
305
-
306
-	/**
307
-	 * @param $addressBookUri
308
-	 * @return array|null
309
-	 */
310
-	public function getAddressBooksByUri($principal, $addressBookUri) {
311
-		$query = $this->db->getQueryBuilder();
312
-		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
313
-			->from('addressbooks')
314
-			->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
315
-			->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
316
-			->setMaxResults(1)
317
-			->execute();
318
-
319
-		$row = $result->fetch();
320
-		$result->closeCursor();
321
-		if ($row === false) {
322
-			return null;
323
-		}
324
-
325
-		$addressBook = [
326
-			'id'  => $row['id'],
327
-			'uri' => $row['uri'],
328
-			'principaluri' => $row['principaluri'],
329
-			'{DAV:}displayname' => $row['displayname'],
330
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
331
-			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
332
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
333
-		];
334
-
335
-		$this->addOwnerPrincipal($addressBook);
336
-
337
-		return $addressBook;
338
-	}
339
-
340
-	/**
341
-	 * Updates properties for an address book.
342
-	 *
343
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
344
-	 * To do the actual updates, you must tell this object which properties
345
-	 * you're going to process with the handle() method.
346
-	 *
347
-	 * Calling the handle method is like telling the PropPatch object "I
348
-	 * promise I can handle updating this property".
349
-	 *
350
-	 * Read the PropPatch documentation for more info and examples.
351
-	 *
352
-	 * @param string $addressBookId
353
-	 * @param \Sabre\DAV\PropPatch $propPatch
354
-	 * @return void
355
-	 */
356
-	function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
357
-		$supportedProperties = [
358
-			'{DAV:}displayname',
359
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
360
-		];
361
-
362
-		/**
363
-		 * @suppress SqlInjectionChecker
364
-		 */
365
-		$propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
366
-
367
-			$updates = [];
368
-			foreach($mutations as $property=>$newValue) {
369
-
370
-				switch($property) {
371
-					case '{DAV:}displayname' :
372
-						$updates['displayname'] = $newValue;
373
-						break;
374
-					case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
375
-						$updates['description'] = $newValue;
376
-						break;
377
-				}
378
-			}
379
-			$query = $this->db->getQueryBuilder();
380
-			$query->update('addressbooks');
381
-
382
-			foreach($updates as $key=>$value) {
383
-				$query->set($key, $query->createNamedParameter($value));
384
-			}
385
-			$query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
386
-			->execute();
387
-
388
-			$this->addChange($addressBookId, "", 2);
389
-
390
-			return true;
391
-
392
-		});
393
-	}
394
-
395
-	/**
396
-	 * Creates a new address book
397
-	 *
398
-	 * @param string $principalUri
399
-	 * @param string $url Just the 'basename' of the url.
400
-	 * @param array $properties
401
-	 * @return int
402
-	 * @throws BadRequest
403
-	 */
404
-	function createAddressBook($principalUri, $url, array $properties) {
405
-		$values = [
406
-			'displayname' => null,
407
-			'description' => null,
408
-			'principaluri' => $principalUri,
409
-			'uri' => $url,
410
-			'synctoken' => 1
411
-		];
412
-
413
-		foreach($properties as $property=>$newValue) {
414
-
415
-			switch($property) {
416
-				case '{DAV:}displayname' :
417
-					$values['displayname'] = $newValue;
418
-					break;
419
-				case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
420
-					$values['description'] = $newValue;
421
-					break;
422
-				default :
423
-					throw new BadRequest('Unknown property: ' . $property);
424
-			}
425
-
426
-		}
427
-
428
-		// Fallback to make sure the displayname is set. Some clients may refuse
429
-		// to work with addressbooks not having a displayname.
430
-		if(is_null($values['displayname'])) {
431
-			$values['displayname'] = $url;
432
-		}
433
-
434
-		$query = $this->db->getQueryBuilder();
435
-		$query->insert('addressbooks')
436
-			->values([
437
-				'uri' => $query->createParameter('uri'),
438
-				'displayname' => $query->createParameter('displayname'),
439
-				'description' => $query->createParameter('description'),
440
-				'principaluri' => $query->createParameter('principaluri'),
441
-				'synctoken' => $query->createParameter('synctoken'),
442
-			])
443
-			->setParameters($values)
444
-			->execute();
445
-
446
-		return $query->getLastInsertId();
447
-	}
448
-
449
-	/**
450
-	 * Deletes an entire addressbook and all its contents
451
-	 *
452
-	 * @param mixed $addressBookId
453
-	 * @return void
454
-	 */
455
-	function deleteAddressBook($addressBookId) {
456
-		$query = $this->db->getQueryBuilder();
457
-		$query->delete('cards')
458
-			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
459
-			->setParameter('addressbookid', $addressBookId)
460
-			->execute();
461
-
462
-		$query->delete('addressbookchanges')
463
-			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
464
-			->setParameter('addressbookid', $addressBookId)
465
-			->execute();
466
-
467
-		$query->delete('addressbooks')
468
-			->where($query->expr()->eq('id', $query->createParameter('id')))
469
-			->setParameter('id', $addressBookId)
470
-			->execute();
471
-
472
-		$this->sharingBackend->deleteAllShares($addressBookId);
473
-
474
-		$query->delete($this->dbCardsPropertiesTable)
475
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
476
-			->execute();
477
-
478
-	}
479
-
480
-	/**
481
-	 * Returns all cards for a specific addressbook id.
482
-	 *
483
-	 * This method should return the following properties for each card:
484
-	 *   * carddata - raw vcard data
485
-	 *   * uri - Some unique url
486
-	 *   * lastmodified - A unix timestamp
487
-	 *
488
-	 * It's recommended to also return the following properties:
489
-	 *   * etag - A unique etag. This must change every time the card changes.
490
-	 *   * size - The size of the card in bytes.
491
-	 *
492
-	 * If these last two properties are provided, less time will be spent
493
-	 * calculating them. If they are specified, you can also ommit carddata.
494
-	 * This may speed up certain requests, especially with large cards.
495
-	 *
496
-	 * @param mixed $addressBookId
497
-	 * @return array
498
-	 */
499
-	function getCards($addressBookId) {
500
-		$query = $this->db->getQueryBuilder();
501
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
502
-			->from('cards')
503
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
504
-
505
-		$cards = [];
506
-
507
-		$result = $query->execute();
508
-		while($row = $result->fetch()) {
509
-			$row['etag'] = '"' . $row['etag'] . '"';
510
-			$row['carddata'] = $this->readBlob($row['carddata']);
511
-			$cards[] = $row;
512
-		}
513
-		$result->closeCursor();
514
-
515
-		return $cards;
516
-	}
517
-
518
-	/**
519
-	 * Returns a specific card.
520
-	 *
521
-	 * The same set of properties must be returned as with getCards. The only
522
-	 * exception is that 'carddata' is absolutely required.
523
-	 *
524
-	 * If the card does not exist, you must return false.
525
-	 *
526
-	 * @param mixed $addressBookId
527
-	 * @param string $cardUri
528
-	 * @return array
529
-	 */
530
-	function getCard($addressBookId, $cardUri) {
531
-		$query = $this->db->getQueryBuilder();
532
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
533
-			->from('cards')
534
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
535
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
536
-			->setMaxResults(1);
537
-
538
-		$result = $query->execute();
539
-		$row = $result->fetch();
540
-		if (!$row) {
541
-			return false;
542
-		}
543
-		$row['etag'] = '"' . $row['etag'] . '"';
544
-		$row['carddata'] = $this->readBlob($row['carddata']);
545
-
546
-		return $row;
547
-	}
548
-
549
-	/**
550
-	 * Returns a list of cards.
551
-	 *
552
-	 * This method should work identical to getCard, but instead return all the
553
-	 * cards in the list as an array.
554
-	 *
555
-	 * If the backend supports this, it may allow for some speed-ups.
556
-	 *
557
-	 * @param mixed $addressBookId
558
-	 * @param string[] $uris
559
-	 * @return array
560
-	 */
561
-	function getMultipleCards($addressBookId, array $uris) {
562
-		if (empty($uris)) {
563
-			return [];
564
-		}
565
-
566
-		$chunks = array_chunk($uris, 100);
567
-		$cards = [];
568
-
569
-		$query = $this->db->getQueryBuilder();
570
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
571
-			->from('cards')
572
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
573
-			->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
574
-
575
-		foreach ($chunks as $uris) {
576
-			$query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
577
-			$result = $query->execute();
578
-
579
-			while ($row = $result->fetch()) {
580
-				$row['etag'] = '"' . $row['etag'] . '"';
581
-				$row['carddata'] = $this->readBlob($row['carddata']);
582
-				$cards[] = $row;
583
-			}
584
-			$result->closeCursor();
585
-		}
586
-		return $cards;
587
-	}
588
-
589
-	/**
590
-	 * Creates a new card.
591
-	 *
592
-	 * The addressbook id will be passed as the first argument. This is the
593
-	 * same id as it is returned from the getAddressBooksForUser method.
594
-	 *
595
-	 * The cardUri is a base uri, and doesn't include the full path. The
596
-	 * cardData argument is the vcard body, and is passed as a string.
597
-	 *
598
-	 * It is possible to return an ETag from this method. This ETag is for the
599
-	 * newly created resource, and must be enclosed with double quotes (that
600
-	 * is, the string itself must contain the double quotes).
601
-	 *
602
-	 * You should only return the ETag if you store the carddata as-is. If a
603
-	 * subsequent GET request on the same card does not have the same body,
604
-	 * byte-by-byte and you did return an ETag here, clients tend to get
605
-	 * confused.
606
-	 *
607
-	 * If you don't return an ETag, you can just return null.
608
-	 *
609
-	 * @param mixed $addressBookId
610
-	 * @param string $cardUri
611
-	 * @param string $cardData
612
-	 * @return string
613
-	 */
614
-	function createCard($addressBookId, $cardUri, $cardData) {
615
-		$etag = md5($cardData);
616
-		$uid = $this->getUID($cardData);
617
-
618
-		$q = $this->db->getQueryBuilder();
619
-		$q->select('uid')
620
-			->from('cards')
621
-			->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
622
-			->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
623
-			->setMaxResults(1);
624
-		$result = $q->execute();
625
-		$count = (bool) $result->fetchColumn();
626
-		$result->closeCursor();
627
-		if ($count) {
628
-			throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
629
-		}
630
-
631
-		$query = $this->db->getQueryBuilder();
632
-		$query->insert('cards')
633
-			->values([
634
-				'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
635
-				'uri' => $query->createNamedParameter($cardUri),
636
-				'lastmodified' => $query->createNamedParameter(time()),
637
-				'addressbookid' => $query->createNamedParameter($addressBookId),
638
-				'size' => $query->createNamedParameter(strlen($cardData)),
639
-				'etag' => $query->createNamedParameter($etag),
640
-				'uid' => $query->createNamedParameter($uid),
641
-			])
642
-			->execute();
643
-
644
-		$this->addChange($addressBookId, $cardUri, 1);
645
-		$this->updateProperties($addressBookId, $cardUri, $cardData);
646
-
647
-		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
648
-			new GenericEvent(null, [
649
-				'addressBookId' => $addressBookId,
650
-				'cardUri' => $cardUri,
651
-				'cardData' => $cardData]));
652
-
653
-		return '"' . $etag . '"';
654
-	}
655
-
656
-	/**
657
-	 * Updates a card.
658
-	 *
659
-	 * The addressbook id will be passed as the first argument. This is the
660
-	 * same id as it is returned from the getAddressBooksForUser method.
661
-	 *
662
-	 * The cardUri is a base uri, and doesn't include the full path. The
663
-	 * cardData argument is the vcard body, and is passed as a string.
664
-	 *
665
-	 * It is possible to return an ETag from this method. This ETag should
666
-	 * match that of the updated resource, and must be enclosed with double
667
-	 * quotes (that is: the string itself must contain the actual quotes).
668
-	 *
669
-	 * You should only return the ETag if you store the carddata as-is. If a
670
-	 * subsequent GET request on the same card does not have the same body,
671
-	 * byte-by-byte and you did return an ETag here, clients tend to get
672
-	 * confused.
673
-	 *
674
-	 * If you don't return an ETag, you can just return null.
675
-	 *
676
-	 * @param mixed $addressBookId
677
-	 * @param string $cardUri
678
-	 * @param string $cardData
679
-	 * @return string
680
-	 */
681
-	function updateCard($addressBookId, $cardUri, $cardData) {
682
-
683
-		$uid = $this->getUID($cardData);
684
-		$etag = md5($cardData);
685
-		$query = $this->db->getQueryBuilder();
686
-		$query->update('cards')
687
-			->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
688
-			->set('lastmodified', $query->createNamedParameter(time()))
689
-			->set('size', $query->createNamedParameter(strlen($cardData)))
690
-			->set('etag', $query->createNamedParameter($etag))
691
-			->set('uid', $query->createNamedParameter($uid))
692
-			->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
693
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
694
-			->execute();
695
-
696
-		$this->addChange($addressBookId, $cardUri, 2);
697
-		$this->updateProperties($addressBookId, $cardUri, $cardData);
698
-
699
-		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
700
-			new GenericEvent(null, [
701
-				'addressBookId' => $addressBookId,
702
-				'cardUri' => $cardUri,
703
-				'cardData' => $cardData]));
704
-
705
-		return '"' . $etag . '"';
706
-	}
707
-
708
-	/**
709
-	 * Deletes a card
710
-	 *
711
-	 * @param mixed $addressBookId
712
-	 * @param string $cardUri
713
-	 * @return bool
714
-	 */
715
-	function deleteCard($addressBookId, $cardUri) {
716
-		try {
717
-			$cardId = $this->getCardId($addressBookId, $cardUri);
718
-		} catch (\InvalidArgumentException $e) {
719
-			$cardId = null;
720
-		}
721
-		$query = $this->db->getQueryBuilder();
722
-		$ret = $query->delete('cards')
723
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
724
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
725
-			->execute();
726
-
727
-		$this->addChange($addressBookId, $cardUri, 3);
728
-
729
-		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
730
-			new GenericEvent(null, [
731
-				'addressBookId' => $addressBookId,
732
-				'cardUri' => $cardUri]));
733
-
734
-		if ($ret === 1) {
735
-			if ($cardId !== null) {
736
-				$this->purgeProperties($addressBookId, $cardId);
737
-			}
738
-			return true;
739
-		}
740
-
741
-		return false;
742
-	}
743
-
744
-	/**
745
-	 * The getChanges method returns all the changes that have happened, since
746
-	 * the specified syncToken in the specified address book.
747
-	 *
748
-	 * This function should return an array, such as the following:
749
-	 *
750
-	 * [
751
-	 *   'syncToken' => 'The current synctoken',
752
-	 *   'added'   => [
753
-	 *      'new.txt',
754
-	 *   ],
755
-	 *   'modified'   => [
756
-	 *      'modified.txt',
757
-	 *   ],
758
-	 *   'deleted' => [
759
-	 *      'foo.php.bak',
760
-	 *      'old.txt'
761
-	 *   ]
762
-	 * ];
763
-	 *
764
-	 * The returned syncToken property should reflect the *current* syncToken
765
-	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
766
-	 * property. This is needed here too, to ensure the operation is atomic.
767
-	 *
768
-	 * If the $syncToken argument is specified as null, this is an initial
769
-	 * sync, and all members should be reported.
770
-	 *
771
-	 * The modified property is an array of nodenames that have changed since
772
-	 * the last token.
773
-	 *
774
-	 * The deleted property is an array with nodenames, that have been deleted
775
-	 * from collection.
776
-	 *
777
-	 * The $syncLevel argument is basically the 'depth' of the report. If it's
778
-	 * 1, you only have to report changes that happened only directly in
779
-	 * immediate descendants. If it's 2, it should also include changes from
780
-	 * the nodes below the child collections. (grandchildren)
781
-	 *
782
-	 * The $limit argument allows a client to specify how many results should
783
-	 * be returned at most. If the limit is not specified, it should be treated
784
-	 * as infinite.
785
-	 *
786
-	 * If the limit (infinite or not) is higher than you're willing to return,
787
-	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
788
-	 *
789
-	 * If the syncToken is expired (due to data cleanup) or unknown, you must
790
-	 * return null.
791
-	 *
792
-	 * The limit is 'suggestive'. You are free to ignore it.
793
-	 *
794
-	 * @param string $addressBookId
795
-	 * @param string $syncToken
796
-	 * @param int $syncLevel
797
-	 * @param int $limit
798
-	 * @return array
799
-	 */
800
-	function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
801
-		// Current synctoken
802
-		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
803
-		$stmt->execute([ $addressBookId ]);
804
-		$currentToken = $stmt->fetchColumn(0);
805
-
806
-		if (is_null($currentToken)) return null;
807
-
808
-		$result = [
809
-			'syncToken' => $currentToken,
810
-			'added'     => [],
811
-			'modified'  => [],
812
-			'deleted'   => [],
813
-		];
814
-
815
-		if ($syncToken) {
816
-
817
-			$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
818
-			if ($limit>0) {
819
-				$query .= " LIMIT " . (int)$limit;
820
-			}
821
-
822
-			// Fetching all changes
823
-			$stmt = $this->db->prepare($query);
824
-			$stmt->execute([$syncToken, $currentToken, $addressBookId]);
825
-
826
-			$changes = [];
827
-
828
-			// This loop ensures that any duplicates are overwritten, only the
829
-			// last change on a node is relevant.
830
-			while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
831
-
832
-				$changes[$row['uri']] = $row['operation'];
833
-
834
-			}
835
-
836
-			foreach($changes as $uri => $operation) {
837
-
838
-				switch($operation) {
839
-					case 1:
840
-						$result['added'][] = $uri;
841
-						break;
842
-					case 2:
843
-						$result['modified'][] = $uri;
844
-						break;
845
-					case 3:
846
-						$result['deleted'][] = $uri;
847
-						break;
848
-				}
849
-
850
-			}
851
-		} else {
852
-			// No synctoken supplied, this is the initial sync.
853
-			$query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
854
-			$stmt = $this->db->prepare($query);
855
-			$stmt->execute([$addressBookId]);
856
-
857
-			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
858
-		}
859
-		return $result;
860
-	}
861
-
862
-	/**
863
-	 * Adds a change record to the addressbookchanges table.
864
-	 *
865
-	 * @param mixed $addressBookId
866
-	 * @param string $objectUri
867
-	 * @param int $operation 1 = add, 2 = modify, 3 = delete
868
-	 * @return void
869
-	 */
870
-	protected function addChange($addressBookId, $objectUri, $operation) {
871
-		$sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
872
-		$stmt = $this->db->prepare($sql);
873
-		$stmt->execute([
874
-			$objectUri,
875
-			$addressBookId,
876
-			$operation,
877
-			$addressBookId
878
-		]);
879
-		$stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
880
-		$stmt->execute([
881
-			$addressBookId
882
-		]);
883
-	}
884
-
885
-	private function readBlob($cardData) {
886
-		if (is_resource($cardData)) {
887
-			return stream_get_contents($cardData);
888
-		}
889
-
890
-		return $cardData;
891
-	}
892
-
893
-	/**
894
-	 * @param IShareable $shareable
895
-	 * @param string[] $add
896
-	 * @param string[] $remove
897
-	 */
898
-	public function updateShares(IShareable $shareable, $add, $remove) {
899
-		$this->sharingBackend->updateShares($shareable, $add, $remove);
900
-	}
901
-
902
-	/**
903
-	 * search contact
904
-	 *
905
-	 * @param int $addressBookId
906
-	 * @param string $pattern which should match within the $searchProperties
907
-	 * @param array $searchProperties defines the properties within the query pattern should match
908
-	 * @param array $options = array() to define the search behavior
909
-	 * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are
910
-	 * @return array an array of contacts which are arrays of key-value-pairs
911
-	 */
912
-	public function search($addressBookId, $pattern, $searchProperties, $options = []) {
913
-		$query = $this->db->getQueryBuilder();
914
-		$query2 = $this->db->getQueryBuilder();
915
-
916
-		$query2->selectDistinct('cp.cardid')->from($this->dbCardsPropertiesTable, 'cp');
917
-		$query2->andWhere($query2->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId)));
918
-		$or = $query2->expr()->orX();
919
-		foreach ($searchProperties as $property) {
920
-			$or->add($query2->expr()->eq('cp.name', $query->createNamedParameter($property)));
921
-		}
922
-		$query2->andWhere($or);
923
-
924
-		// No need for like when the pattern is empty
925
-		if ('' !== $pattern) {
926
-			if(\array_key_exists('escape_like_param', $options) && $options['escape_like_param'] === false) {
927
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter($pattern)));
928
-			} else {
929
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
930
-			}
931
-		}
932
-
933
-		$query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c')
934
-			->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL())));
935
-
936
-		$result = $query->execute();
937
-		$cards = $result->fetchAll();
938
-
939
-		$result->closeCursor();
940
-
941
-		return array_map(function($array) {
942
-			$array['carddata'] = $this->readBlob($array['carddata']);
943
-			return $array;
944
-		}, $cards);
945
-	}
946
-
947
-	/**
948
-	 * @param int $bookId
949
-	 * @param string $name
950
-	 * @return array
951
-	 */
952
-	public function collectCardProperties($bookId, $name) {
953
-		$query = $this->db->getQueryBuilder();
954
-		$result = $query->selectDistinct('value')
955
-			->from($this->dbCardsPropertiesTable)
956
-			->where($query->expr()->eq('name', $query->createNamedParameter($name)))
957
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
958
-			->execute();
959
-
960
-		$all = $result->fetchAll(PDO::FETCH_COLUMN);
961
-		$result->closeCursor();
962
-
963
-		return $all;
964
-	}
965
-
966
-	/**
967
-	 * get URI from a given contact
968
-	 *
969
-	 * @param int $id
970
-	 * @return string
971
-	 */
972
-	public function getCardUri($id) {
973
-		$query = $this->db->getQueryBuilder();
974
-		$query->select('uri')->from($this->dbCardsTable)
975
-				->where($query->expr()->eq('id', $query->createParameter('id')))
976
-				->setParameter('id', $id);
977
-
978
-		$result = $query->execute();
979
-		$uri = $result->fetch();
980
-		$result->closeCursor();
981
-
982
-		if (!isset($uri['uri'])) {
983
-			throw new \InvalidArgumentException('Card does not exists: ' . $id);
984
-		}
985
-
986
-		return $uri['uri'];
987
-	}
988
-
989
-	/**
990
-	 * return contact with the given URI
991
-	 *
992
-	 * @param int $addressBookId
993
-	 * @param string $uri
994
-	 * @returns array
995
-	 */
996
-	public function getContact($addressBookId, $uri) {
997
-		$result = [];
998
-		$query = $this->db->getQueryBuilder();
999
-		$query->select('*')->from($this->dbCardsTable)
1000
-				->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1001
-				->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1002
-		$queryResult = $query->execute();
1003
-		$contact = $queryResult->fetch();
1004
-		$queryResult->closeCursor();
1005
-
1006
-		if (is_array($contact)) {
1007
-			$result = $contact;
1008
-		}
1009
-
1010
-		return $result;
1011
-	}
1012
-
1013
-	/**
1014
-	 * Returns the list of people whom this address book is shared with.
1015
-	 *
1016
-	 * Every element in this array should have the following properties:
1017
-	 *   * href - Often a mailto: address
1018
-	 *   * commonName - Optional, for example a first + last name
1019
-	 *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
1020
-	 *   * readOnly - boolean
1021
-	 *   * summary - Optional, a description for the share
1022
-	 *
1023
-	 * @return array
1024
-	 */
1025
-	public function getShares($addressBookId) {
1026
-		return $this->sharingBackend->getShares($addressBookId);
1027
-	}
1028
-
1029
-	/**
1030
-	 * update properties table
1031
-	 *
1032
-	 * @param int $addressBookId
1033
-	 * @param string $cardUri
1034
-	 * @param string $vCardSerialized
1035
-	 */
1036
-	protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1037
-		$cardId = $this->getCardId($addressBookId, $cardUri);
1038
-		$vCard = $this->readCard($vCardSerialized);
1039
-
1040
-		$this->purgeProperties($addressBookId, $cardId);
1041
-
1042
-		$query = $this->db->getQueryBuilder();
1043
-		$query->insert($this->dbCardsPropertiesTable)
1044
-			->values(
1045
-				[
1046
-					'addressbookid' => $query->createNamedParameter($addressBookId),
1047
-					'cardid' => $query->createNamedParameter($cardId),
1048
-					'name' => $query->createParameter('name'),
1049
-					'value' => $query->createParameter('value'),
1050
-					'preferred' => $query->createParameter('preferred')
1051
-				]
1052
-			);
1053
-
1054
-		foreach ($vCard->children() as $property) {
1055
-			if(!in_array($property->name, self::$indexProperties)) {
1056
-				continue;
1057
-			}
1058
-			$preferred = 0;
1059
-			foreach($property->parameters as $parameter) {
1060
-				if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1061
-					$preferred = 1;
1062
-					break;
1063
-				}
1064
-			}
1065
-			$query->setParameter('name', $property->name);
1066
-			$query->setParameter('value', mb_substr($property->getValue(), 0, 254));
1067
-			$query->setParameter('preferred', $preferred);
1068
-			$query->execute();
1069
-		}
1070
-	}
1071
-
1072
-	/**
1073
-	 * read vCard data into a vCard object
1074
-	 *
1075
-	 * @param string $cardData
1076
-	 * @return VCard
1077
-	 */
1078
-	protected function readCard($cardData) {
1079
-		return  Reader::read($cardData);
1080
-	}
1081
-
1082
-	/**
1083
-	 * delete all properties from a given card
1084
-	 *
1085
-	 * @param int $addressBookId
1086
-	 * @param int $cardId
1087
-	 */
1088
-	protected function purgeProperties($addressBookId, $cardId) {
1089
-		$query = $this->db->getQueryBuilder();
1090
-		$query->delete($this->dbCardsPropertiesTable)
1091
-			->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1092
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1093
-		$query->execute();
1094
-	}
1095
-
1096
-	/**
1097
-	 * get ID from a given contact
1098
-	 *
1099
-	 * @param int $addressBookId
1100
-	 * @param string $uri
1101
-	 * @return int
1102
-	 */
1103
-	protected function getCardId($addressBookId, $uri) {
1104
-		$query = $this->db->getQueryBuilder();
1105
-		$query->select('id')->from($this->dbCardsTable)
1106
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1107
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1108
-
1109
-		$result = $query->execute();
1110
-		$cardIds = $result->fetch();
1111
-		$result->closeCursor();
1112
-
1113
-		if (!isset($cardIds['id'])) {
1114
-			throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1115
-		}
1116
-
1117
-		return (int)$cardIds['id'];
1118
-	}
1119
-
1120
-	/**
1121
-	 * For shared address books the sharee is set in the ACL of the address book
1122
-	 * @param $addressBookId
1123
-	 * @param $acl
1124
-	 * @return array
1125
-	 */
1126
-	public function applyShareAcl($addressBookId, $acl) {
1127
-		return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1128
-	}
1129
-
1130
-	private function convertPrincipal($principalUri, $toV2) {
1131
-		if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1132
-			list(, $name) = \Sabre\Uri\split($principalUri);
1133
-			if ($toV2 === true) {
1134
-				return "principals/users/$name";
1135
-			}
1136
-			return "principals/$name";
1137
-		}
1138
-		return $principalUri;
1139
-	}
1140
-
1141
-	private function addOwnerPrincipal(&$addressbookInfo) {
1142
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1143
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1144
-		if (isset($addressbookInfo[$ownerPrincipalKey])) {
1145
-			$uri = $addressbookInfo[$ownerPrincipalKey];
1146
-		} else {
1147
-			$uri = $addressbookInfo['principaluri'];
1148
-		}
1149
-
1150
-		$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1151
-		if (isset($principalInformation['{DAV:}displayname'])) {
1152
-			$addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1153
-		}
1154
-	}
1155
-
1156
-	/**
1157
-	 * Extract UID from vcard
1158
-	 *
1159
-	 * @param string $cardData the vcard raw data
1160
-	 * @return string the uid
1161
-	 * @throws BadRequest if no UID is available
1162
-	 */
1163
-	private function getUID($cardData) {
1164
-		if ($cardData != '') {
1165
-			$vCard = Reader::read($cardData);
1166
-			if ($vCard->UID) {
1167
-				$uid = $vCard->UID->getValue();
1168
-				return $uid;
1169
-			}
1170
-			// should already be handled, but just in case
1171
-			throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
1172
-		}
1173
-		// should already be handled, but just in case
1174
-		throw new BadRequest('vCard can not be empty');
1175
-	}
58
+    const PERSONAL_ADDRESSBOOK_URI = 'contacts';
59
+    const PERSONAL_ADDRESSBOOK_NAME = 'Contacts';
60
+
61
+    /** @var Principal */
62
+    private $principalBackend;
63
+
64
+    /** @var string */
65
+    private $dbCardsTable = 'cards';
66
+
67
+    /** @var string */
68
+    private $dbCardsPropertiesTable = 'cards_properties';
69
+
70
+    /** @var IDBConnection */
71
+    private $db;
72
+
73
+    /** @var Backend */
74
+    private $sharingBackend;
75
+
76
+    /** @var array properties to index */
77
+    public static $indexProperties = [
78
+            'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
79
+            'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD'];
80
+
81
+    /**
82
+     * @var string[] Map of uid => display name
83
+     */
84
+    protected $userDisplayNames;
85
+
86
+    /** @var IUserManager */
87
+    private $userManager;
88
+
89
+    /** @var EventDispatcherInterface */
90
+    private $dispatcher;
91
+
92
+    /**
93
+     * CardDavBackend constructor.
94
+     *
95
+     * @param IDBConnection $db
96
+     * @param Principal $principalBackend
97
+     * @param IUserManager $userManager
98
+     * @param IGroupManager $groupManager
99
+     * @param EventDispatcherInterface $dispatcher
100
+     */
101
+    public function __construct(IDBConnection $db,
102
+                                Principal $principalBackend,
103
+                                IUserManager $userManager,
104
+                                IGroupManager $groupManager,
105
+                                EventDispatcherInterface $dispatcher) {
106
+        $this->db = $db;
107
+        $this->principalBackend = $principalBackend;
108
+        $this->userManager = $userManager;
109
+        $this->dispatcher = $dispatcher;
110
+        $this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'addressbook');
111
+    }
112
+
113
+    /**
114
+     * Return the number of address books for a principal
115
+     *
116
+     * @param $principalUri
117
+     * @return int
118
+     */
119
+    public function getAddressBooksForUserCount($principalUri) {
120
+        $principalUri = $this->convertPrincipal($principalUri, true);
121
+        $query = $this->db->getQueryBuilder();
122
+        $query->select($query->func()->count('*'))
123
+            ->from('addressbooks')
124
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
125
+
126
+        return (int)$query->execute()->fetchColumn();
127
+    }
128
+
129
+    /**
130
+     * Returns the list of address books for a specific user.
131
+     *
132
+     * Every addressbook should have the following properties:
133
+     *   id - an arbitrary unique id
134
+     *   uri - the 'basename' part of the url
135
+     *   principaluri - Same as the passed parameter
136
+     *
137
+     * Any additional clark-notation property may be passed besides this. Some
138
+     * common ones are :
139
+     *   {DAV:}displayname
140
+     *   {urn:ietf:params:xml:ns:carddav}addressbook-description
141
+     *   {http://calendarserver.org/ns/}getctag
142
+     *
143
+     * @param string $principalUri
144
+     * @return array
145
+     */
146
+    function getAddressBooksForUser($principalUri) {
147
+        $principalUriOriginal = $principalUri;
148
+        $principalUri = $this->convertPrincipal($principalUri, true);
149
+        $query = $this->db->getQueryBuilder();
150
+        $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
151
+            ->from('addressbooks')
152
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
153
+
154
+        $addressBooks = [];
155
+
156
+        $result = $query->execute();
157
+        while($row = $result->fetch()) {
158
+            $addressBooks[$row['id']] = [
159
+                'id'  => $row['id'],
160
+                'uri' => $row['uri'],
161
+                'principaluri' => $this->convertPrincipal($row['principaluri'], false),
162
+                '{DAV:}displayname' => $row['displayname'],
163
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
164
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
165
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
166
+            ];
167
+
168
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
169
+        }
170
+        $result->closeCursor();
171
+
172
+        // query for shared addressbooks
173
+        $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
174
+        $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
175
+
176
+        $principals = array_map(function($principal) {
177
+            return urldecode($principal);
178
+        }, $principals);
179
+        $principals[]= $principalUri;
180
+
181
+        $query = $this->db->getQueryBuilder();
182
+        $result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
183
+            ->from('dav_shares', 's')
184
+            ->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
185
+            ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
186
+            ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
187
+            ->setParameter('type', 'addressbook')
188
+            ->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
189
+            ->execute();
190
+
191
+        $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
192
+        while($row = $result->fetch()) {
193
+            if ($row['principaluri'] === $principalUri) {
194
+                continue;
195
+            }
196
+
197
+            $readOnly = (int) $row['access'] === Backend::ACCESS_READ;
198
+            if (isset($addressBooks[$row['id']])) {
199
+                if ($readOnly) {
200
+                    // New share can not have more permissions then the old one.
201
+                    continue;
202
+                }
203
+                if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
204
+                    $addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
205
+                    // Old share is already read-write, no more permissions can be gained
206
+                    continue;
207
+                }
208
+            }
209
+
210
+            list(, $name) = \Sabre\Uri\split($row['principaluri']);
211
+            $uri = $row['uri'] . '_shared_by_' . $name;
212
+            $displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
213
+
214
+            $addressBooks[$row['id']] = [
215
+                'id'  => $row['id'],
216
+                'uri' => $uri,
217
+                'principaluri' => $principalUriOriginal,
218
+                '{DAV:}displayname' => $displayName,
219
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
220
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
221
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
222
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
223
+                $readOnlyPropertyName => $readOnly,
224
+            ];
225
+
226
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
227
+        }
228
+        $result->closeCursor();
229
+
230
+        return array_values($addressBooks);
231
+    }
232
+
233
+    public function getUsersOwnAddressBooks($principalUri) {
234
+        $principalUri = $this->convertPrincipal($principalUri, true);
235
+        $query = $this->db->getQueryBuilder();
236
+        $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
237
+                ->from('addressbooks')
238
+                ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
239
+
240
+        $addressBooks = [];
241
+
242
+        $result = $query->execute();
243
+        while($row = $result->fetch()) {
244
+            $addressBooks[$row['id']] = [
245
+                'id'  => $row['id'],
246
+                'uri' => $row['uri'],
247
+                'principaluri' => $this->convertPrincipal($row['principaluri'], false),
248
+                '{DAV:}displayname' => $row['displayname'],
249
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
250
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
251
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
252
+            ];
253
+
254
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
255
+        }
256
+        $result->closeCursor();
257
+
258
+        return array_values($addressBooks);
259
+    }
260
+
261
+    private function getUserDisplayName($uid) {
262
+        if (!isset($this->userDisplayNames[$uid])) {
263
+            $user = $this->userManager->get($uid);
264
+
265
+            if ($user instanceof IUser) {
266
+                $this->userDisplayNames[$uid] = $user->getDisplayName();
267
+            } else {
268
+                $this->userDisplayNames[$uid] = $uid;
269
+            }
270
+        }
271
+
272
+        return $this->userDisplayNames[$uid];
273
+    }
274
+
275
+    /**
276
+     * @param int $addressBookId
277
+     */
278
+    public function getAddressBookById($addressBookId) {
279
+        $query = $this->db->getQueryBuilder();
280
+        $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
281
+            ->from('addressbooks')
282
+            ->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
283
+            ->execute();
284
+
285
+        $row = $result->fetch();
286
+        $result->closeCursor();
287
+        if ($row === false) {
288
+            return null;
289
+        }
290
+
291
+        $addressBook = [
292
+            'id'  => $row['id'],
293
+            'uri' => $row['uri'],
294
+            'principaluri' => $row['principaluri'],
295
+            '{DAV:}displayname' => $row['displayname'],
296
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
297
+            '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
298
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
299
+        ];
300
+
301
+        $this->addOwnerPrincipal($addressBook);
302
+
303
+        return $addressBook;
304
+    }
305
+
306
+    /**
307
+     * @param $addressBookUri
308
+     * @return array|null
309
+     */
310
+    public function getAddressBooksByUri($principal, $addressBookUri) {
311
+        $query = $this->db->getQueryBuilder();
312
+        $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
313
+            ->from('addressbooks')
314
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
315
+            ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
316
+            ->setMaxResults(1)
317
+            ->execute();
318
+
319
+        $row = $result->fetch();
320
+        $result->closeCursor();
321
+        if ($row === false) {
322
+            return null;
323
+        }
324
+
325
+        $addressBook = [
326
+            'id'  => $row['id'],
327
+            'uri' => $row['uri'],
328
+            'principaluri' => $row['principaluri'],
329
+            '{DAV:}displayname' => $row['displayname'],
330
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
331
+            '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
332
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
333
+        ];
334
+
335
+        $this->addOwnerPrincipal($addressBook);
336
+
337
+        return $addressBook;
338
+    }
339
+
340
+    /**
341
+     * Updates properties for an address book.
342
+     *
343
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
344
+     * To do the actual updates, you must tell this object which properties
345
+     * you're going to process with the handle() method.
346
+     *
347
+     * Calling the handle method is like telling the PropPatch object "I
348
+     * promise I can handle updating this property".
349
+     *
350
+     * Read the PropPatch documentation for more info and examples.
351
+     *
352
+     * @param string $addressBookId
353
+     * @param \Sabre\DAV\PropPatch $propPatch
354
+     * @return void
355
+     */
356
+    function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
357
+        $supportedProperties = [
358
+            '{DAV:}displayname',
359
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description',
360
+        ];
361
+
362
+        /**
363
+         * @suppress SqlInjectionChecker
364
+         */
365
+        $propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
366
+
367
+            $updates = [];
368
+            foreach($mutations as $property=>$newValue) {
369
+
370
+                switch($property) {
371
+                    case '{DAV:}displayname' :
372
+                        $updates['displayname'] = $newValue;
373
+                        break;
374
+                    case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
375
+                        $updates['description'] = $newValue;
376
+                        break;
377
+                }
378
+            }
379
+            $query = $this->db->getQueryBuilder();
380
+            $query->update('addressbooks');
381
+
382
+            foreach($updates as $key=>$value) {
383
+                $query->set($key, $query->createNamedParameter($value));
384
+            }
385
+            $query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
386
+            ->execute();
387
+
388
+            $this->addChange($addressBookId, "", 2);
389
+
390
+            return true;
391
+
392
+        });
393
+    }
394
+
395
+    /**
396
+     * Creates a new address book
397
+     *
398
+     * @param string $principalUri
399
+     * @param string $url Just the 'basename' of the url.
400
+     * @param array $properties
401
+     * @return int
402
+     * @throws BadRequest
403
+     */
404
+    function createAddressBook($principalUri, $url, array $properties) {
405
+        $values = [
406
+            'displayname' => null,
407
+            'description' => null,
408
+            'principaluri' => $principalUri,
409
+            'uri' => $url,
410
+            'synctoken' => 1
411
+        ];
412
+
413
+        foreach($properties as $property=>$newValue) {
414
+
415
+            switch($property) {
416
+                case '{DAV:}displayname' :
417
+                    $values['displayname'] = $newValue;
418
+                    break;
419
+                case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
420
+                    $values['description'] = $newValue;
421
+                    break;
422
+                default :
423
+                    throw new BadRequest('Unknown property: ' . $property);
424
+            }
425
+
426
+        }
427
+
428
+        // Fallback to make sure the displayname is set. Some clients may refuse
429
+        // to work with addressbooks not having a displayname.
430
+        if(is_null($values['displayname'])) {
431
+            $values['displayname'] = $url;
432
+        }
433
+
434
+        $query = $this->db->getQueryBuilder();
435
+        $query->insert('addressbooks')
436
+            ->values([
437
+                'uri' => $query->createParameter('uri'),
438
+                'displayname' => $query->createParameter('displayname'),
439
+                'description' => $query->createParameter('description'),
440
+                'principaluri' => $query->createParameter('principaluri'),
441
+                'synctoken' => $query->createParameter('synctoken'),
442
+            ])
443
+            ->setParameters($values)
444
+            ->execute();
445
+
446
+        return $query->getLastInsertId();
447
+    }
448
+
449
+    /**
450
+     * Deletes an entire addressbook and all its contents
451
+     *
452
+     * @param mixed $addressBookId
453
+     * @return void
454
+     */
455
+    function deleteAddressBook($addressBookId) {
456
+        $query = $this->db->getQueryBuilder();
457
+        $query->delete('cards')
458
+            ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
459
+            ->setParameter('addressbookid', $addressBookId)
460
+            ->execute();
461
+
462
+        $query->delete('addressbookchanges')
463
+            ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
464
+            ->setParameter('addressbookid', $addressBookId)
465
+            ->execute();
466
+
467
+        $query->delete('addressbooks')
468
+            ->where($query->expr()->eq('id', $query->createParameter('id')))
469
+            ->setParameter('id', $addressBookId)
470
+            ->execute();
471
+
472
+        $this->sharingBackend->deleteAllShares($addressBookId);
473
+
474
+        $query->delete($this->dbCardsPropertiesTable)
475
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
476
+            ->execute();
477
+
478
+    }
479
+
480
+    /**
481
+     * Returns all cards for a specific addressbook id.
482
+     *
483
+     * This method should return the following properties for each card:
484
+     *   * carddata - raw vcard data
485
+     *   * uri - Some unique url
486
+     *   * lastmodified - A unix timestamp
487
+     *
488
+     * It's recommended to also return the following properties:
489
+     *   * etag - A unique etag. This must change every time the card changes.
490
+     *   * size - The size of the card in bytes.
491
+     *
492
+     * If these last two properties are provided, less time will be spent
493
+     * calculating them. If they are specified, you can also ommit carddata.
494
+     * This may speed up certain requests, especially with large cards.
495
+     *
496
+     * @param mixed $addressBookId
497
+     * @return array
498
+     */
499
+    function getCards($addressBookId) {
500
+        $query = $this->db->getQueryBuilder();
501
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
502
+            ->from('cards')
503
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
504
+
505
+        $cards = [];
506
+
507
+        $result = $query->execute();
508
+        while($row = $result->fetch()) {
509
+            $row['etag'] = '"' . $row['etag'] . '"';
510
+            $row['carddata'] = $this->readBlob($row['carddata']);
511
+            $cards[] = $row;
512
+        }
513
+        $result->closeCursor();
514
+
515
+        return $cards;
516
+    }
517
+
518
+    /**
519
+     * Returns a specific card.
520
+     *
521
+     * The same set of properties must be returned as with getCards. The only
522
+     * exception is that 'carddata' is absolutely required.
523
+     *
524
+     * If the card does not exist, you must return false.
525
+     *
526
+     * @param mixed $addressBookId
527
+     * @param string $cardUri
528
+     * @return array
529
+     */
530
+    function getCard($addressBookId, $cardUri) {
531
+        $query = $this->db->getQueryBuilder();
532
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
533
+            ->from('cards')
534
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
535
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
536
+            ->setMaxResults(1);
537
+
538
+        $result = $query->execute();
539
+        $row = $result->fetch();
540
+        if (!$row) {
541
+            return false;
542
+        }
543
+        $row['etag'] = '"' . $row['etag'] . '"';
544
+        $row['carddata'] = $this->readBlob($row['carddata']);
545
+
546
+        return $row;
547
+    }
548
+
549
+    /**
550
+     * Returns a list of cards.
551
+     *
552
+     * This method should work identical to getCard, but instead return all the
553
+     * cards in the list as an array.
554
+     *
555
+     * If the backend supports this, it may allow for some speed-ups.
556
+     *
557
+     * @param mixed $addressBookId
558
+     * @param string[] $uris
559
+     * @return array
560
+     */
561
+    function getMultipleCards($addressBookId, array $uris) {
562
+        if (empty($uris)) {
563
+            return [];
564
+        }
565
+
566
+        $chunks = array_chunk($uris, 100);
567
+        $cards = [];
568
+
569
+        $query = $this->db->getQueryBuilder();
570
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
571
+            ->from('cards')
572
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
573
+            ->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
574
+
575
+        foreach ($chunks as $uris) {
576
+            $query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
577
+            $result = $query->execute();
578
+
579
+            while ($row = $result->fetch()) {
580
+                $row['etag'] = '"' . $row['etag'] . '"';
581
+                $row['carddata'] = $this->readBlob($row['carddata']);
582
+                $cards[] = $row;
583
+            }
584
+            $result->closeCursor();
585
+        }
586
+        return $cards;
587
+    }
588
+
589
+    /**
590
+     * Creates a new card.
591
+     *
592
+     * The addressbook id will be passed as the first argument. This is the
593
+     * same id as it is returned from the getAddressBooksForUser method.
594
+     *
595
+     * The cardUri is a base uri, and doesn't include the full path. The
596
+     * cardData argument is the vcard body, and is passed as a string.
597
+     *
598
+     * It is possible to return an ETag from this method. This ETag is for the
599
+     * newly created resource, and must be enclosed with double quotes (that
600
+     * is, the string itself must contain the double quotes).
601
+     *
602
+     * You should only return the ETag if you store the carddata as-is. If a
603
+     * subsequent GET request on the same card does not have the same body,
604
+     * byte-by-byte and you did return an ETag here, clients tend to get
605
+     * confused.
606
+     *
607
+     * If you don't return an ETag, you can just return null.
608
+     *
609
+     * @param mixed $addressBookId
610
+     * @param string $cardUri
611
+     * @param string $cardData
612
+     * @return string
613
+     */
614
+    function createCard($addressBookId, $cardUri, $cardData) {
615
+        $etag = md5($cardData);
616
+        $uid = $this->getUID($cardData);
617
+
618
+        $q = $this->db->getQueryBuilder();
619
+        $q->select('uid')
620
+            ->from('cards')
621
+            ->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
622
+            ->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
623
+            ->setMaxResults(1);
624
+        $result = $q->execute();
625
+        $count = (bool) $result->fetchColumn();
626
+        $result->closeCursor();
627
+        if ($count) {
628
+            throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
629
+        }
630
+
631
+        $query = $this->db->getQueryBuilder();
632
+        $query->insert('cards')
633
+            ->values([
634
+                'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
635
+                'uri' => $query->createNamedParameter($cardUri),
636
+                'lastmodified' => $query->createNamedParameter(time()),
637
+                'addressbookid' => $query->createNamedParameter($addressBookId),
638
+                'size' => $query->createNamedParameter(strlen($cardData)),
639
+                'etag' => $query->createNamedParameter($etag),
640
+                'uid' => $query->createNamedParameter($uid),
641
+            ])
642
+            ->execute();
643
+
644
+        $this->addChange($addressBookId, $cardUri, 1);
645
+        $this->updateProperties($addressBookId, $cardUri, $cardData);
646
+
647
+        $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
648
+            new GenericEvent(null, [
649
+                'addressBookId' => $addressBookId,
650
+                'cardUri' => $cardUri,
651
+                'cardData' => $cardData]));
652
+
653
+        return '"' . $etag . '"';
654
+    }
655
+
656
+    /**
657
+     * Updates a card.
658
+     *
659
+     * The addressbook id will be passed as the first argument. This is the
660
+     * same id as it is returned from the getAddressBooksForUser method.
661
+     *
662
+     * The cardUri is a base uri, and doesn't include the full path. The
663
+     * cardData argument is the vcard body, and is passed as a string.
664
+     *
665
+     * It is possible to return an ETag from this method. This ETag should
666
+     * match that of the updated resource, and must be enclosed with double
667
+     * quotes (that is: the string itself must contain the actual quotes).
668
+     *
669
+     * You should only return the ETag if you store the carddata as-is. If a
670
+     * subsequent GET request on the same card does not have the same body,
671
+     * byte-by-byte and you did return an ETag here, clients tend to get
672
+     * confused.
673
+     *
674
+     * If you don't return an ETag, you can just return null.
675
+     *
676
+     * @param mixed $addressBookId
677
+     * @param string $cardUri
678
+     * @param string $cardData
679
+     * @return string
680
+     */
681
+    function updateCard($addressBookId, $cardUri, $cardData) {
682
+
683
+        $uid = $this->getUID($cardData);
684
+        $etag = md5($cardData);
685
+        $query = $this->db->getQueryBuilder();
686
+        $query->update('cards')
687
+            ->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
688
+            ->set('lastmodified', $query->createNamedParameter(time()))
689
+            ->set('size', $query->createNamedParameter(strlen($cardData)))
690
+            ->set('etag', $query->createNamedParameter($etag))
691
+            ->set('uid', $query->createNamedParameter($uid))
692
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
693
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
694
+            ->execute();
695
+
696
+        $this->addChange($addressBookId, $cardUri, 2);
697
+        $this->updateProperties($addressBookId, $cardUri, $cardData);
698
+
699
+        $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
700
+            new GenericEvent(null, [
701
+                'addressBookId' => $addressBookId,
702
+                'cardUri' => $cardUri,
703
+                'cardData' => $cardData]));
704
+
705
+        return '"' . $etag . '"';
706
+    }
707
+
708
+    /**
709
+     * Deletes a card
710
+     *
711
+     * @param mixed $addressBookId
712
+     * @param string $cardUri
713
+     * @return bool
714
+     */
715
+    function deleteCard($addressBookId, $cardUri) {
716
+        try {
717
+            $cardId = $this->getCardId($addressBookId, $cardUri);
718
+        } catch (\InvalidArgumentException $e) {
719
+            $cardId = null;
720
+        }
721
+        $query = $this->db->getQueryBuilder();
722
+        $ret = $query->delete('cards')
723
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
724
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
725
+            ->execute();
726
+
727
+        $this->addChange($addressBookId, $cardUri, 3);
728
+
729
+        $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
730
+            new GenericEvent(null, [
731
+                'addressBookId' => $addressBookId,
732
+                'cardUri' => $cardUri]));
733
+
734
+        if ($ret === 1) {
735
+            if ($cardId !== null) {
736
+                $this->purgeProperties($addressBookId, $cardId);
737
+            }
738
+            return true;
739
+        }
740
+
741
+        return false;
742
+    }
743
+
744
+    /**
745
+     * The getChanges method returns all the changes that have happened, since
746
+     * the specified syncToken in the specified address book.
747
+     *
748
+     * This function should return an array, such as the following:
749
+     *
750
+     * [
751
+     *   'syncToken' => 'The current synctoken',
752
+     *   'added'   => [
753
+     *      'new.txt',
754
+     *   ],
755
+     *   'modified'   => [
756
+     *      'modified.txt',
757
+     *   ],
758
+     *   'deleted' => [
759
+     *      'foo.php.bak',
760
+     *      'old.txt'
761
+     *   ]
762
+     * ];
763
+     *
764
+     * The returned syncToken property should reflect the *current* syncToken
765
+     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
766
+     * property. This is needed here too, to ensure the operation is atomic.
767
+     *
768
+     * If the $syncToken argument is specified as null, this is an initial
769
+     * sync, and all members should be reported.
770
+     *
771
+     * The modified property is an array of nodenames that have changed since
772
+     * the last token.
773
+     *
774
+     * The deleted property is an array with nodenames, that have been deleted
775
+     * from collection.
776
+     *
777
+     * The $syncLevel argument is basically the 'depth' of the report. If it's
778
+     * 1, you only have to report changes that happened only directly in
779
+     * immediate descendants. If it's 2, it should also include changes from
780
+     * the nodes below the child collections. (grandchildren)
781
+     *
782
+     * The $limit argument allows a client to specify how many results should
783
+     * be returned at most. If the limit is not specified, it should be treated
784
+     * as infinite.
785
+     *
786
+     * If the limit (infinite or not) is higher than you're willing to return,
787
+     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
788
+     *
789
+     * If the syncToken is expired (due to data cleanup) or unknown, you must
790
+     * return null.
791
+     *
792
+     * The limit is 'suggestive'. You are free to ignore it.
793
+     *
794
+     * @param string $addressBookId
795
+     * @param string $syncToken
796
+     * @param int $syncLevel
797
+     * @param int $limit
798
+     * @return array
799
+     */
800
+    function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
801
+        // Current synctoken
802
+        $stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
803
+        $stmt->execute([ $addressBookId ]);
804
+        $currentToken = $stmt->fetchColumn(0);
805
+
806
+        if (is_null($currentToken)) return null;
807
+
808
+        $result = [
809
+            'syncToken' => $currentToken,
810
+            'added'     => [],
811
+            'modified'  => [],
812
+            'deleted'   => [],
813
+        ];
814
+
815
+        if ($syncToken) {
816
+
817
+            $query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
818
+            if ($limit>0) {
819
+                $query .= " LIMIT " . (int)$limit;
820
+            }
821
+
822
+            // Fetching all changes
823
+            $stmt = $this->db->prepare($query);
824
+            $stmt->execute([$syncToken, $currentToken, $addressBookId]);
825
+
826
+            $changes = [];
827
+
828
+            // This loop ensures that any duplicates are overwritten, only the
829
+            // last change on a node is relevant.
830
+            while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
831
+
832
+                $changes[$row['uri']] = $row['operation'];
833
+
834
+            }
835
+
836
+            foreach($changes as $uri => $operation) {
837
+
838
+                switch($operation) {
839
+                    case 1:
840
+                        $result['added'][] = $uri;
841
+                        break;
842
+                    case 2:
843
+                        $result['modified'][] = $uri;
844
+                        break;
845
+                    case 3:
846
+                        $result['deleted'][] = $uri;
847
+                        break;
848
+                }
849
+
850
+            }
851
+        } else {
852
+            // No synctoken supplied, this is the initial sync.
853
+            $query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
854
+            $stmt = $this->db->prepare($query);
855
+            $stmt->execute([$addressBookId]);
856
+
857
+            $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
858
+        }
859
+        return $result;
860
+    }
861
+
862
+    /**
863
+     * Adds a change record to the addressbookchanges table.
864
+     *
865
+     * @param mixed $addressBookId
866
+     * @param string $objectUri
867
+     * @param int $operation 1 = add, 2 = modify, 3 = delete
868
+     * @return void
869
+     */
870
+    protected function addChange($addressBookId, $objectUri, $operation) {
871
+        $sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
872
+        $stmt = $this->db->prepare($sql);
873
+        $stmt->execute([
874
+            $objectUri,
875
+            $addressBookId,
876
+            $operation,
877
+            $addressBookId
878
+        ]);
879
+        $stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
880
+        $stmt->execute([
881
+            $addressBookId
882
+        ]);
883
+    }
884
+
885
+    private function readBlob($cardData) {
886
+        if (is_resource($cardData)) {
887
+            return stream_get_contents($cardData);
888
+        }
889
+
890
+        return $cardData;
891
+    }
892
+
893
+    /**
894
+     * @param IShareable $shareable
895
+     * @param string[] $add
896
+     * @param string[] $remove
897
+     */
898
+    public function updateShares(IShareable $shareable, $add, $remove) {
899
+        $this->sharingBackend->updateShares($shareable, $add, $remove);
900
+    }
901
+
902
+    /**
903
+     * search contact
904
+     *
905
+     * @param int $addressBookId
906
+     * @param string $pattern which should match within the $searchProperties
907
+     * @param array $searchProperties defines the properties within the query pattern should match
908
+     * @param array $options = array() to define the search behavior
909
+     * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are
910
+     * @return array an array of contacts which are arrays of key-value-pairs
911
+     */
912
+    public function search($addressBookId, $pattern, $searchProperties, $options = []) {
913
+        $query = $this->db->getQueryBuilder();
914
+        $query2 = $this->db->getQueryBuilder();
915
+
916
+        $query2->selectDistinct('cp.cardid')->from($this->dbCardsPropertiesTable, 'cp');
917
+        $query2->andWhere($query2->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId)));
918
+        $or = $query2->expr()->orX();
919
+        foreach ($searchProperties as $property) {
920
+            $or->add($query2->expr()->eq('cp.name', $query->createNamedParameter($property)));
921
+        }
922
+        $query2->andWhere($or);
923
+
924
+        // No need for like when the pattern is empty
925
+        if ('' !== $pattern) {
926
+            if(\array_key_exists('escape_like_param', $options) && $options['escape_like_param'] === false) {
927
+                $query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter($pattern)));
928
+            } else {
929
+                $query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
930
+            }
931
+        }
932
+
933
+        $query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c')
934
+            ->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL())));
935
+
936
+        $result = $query->execute();
937
+        $cards = $result->fetchAll();
938
+
939
+        $result->closeCursor();
940
+
941
+        return array_map(function($array) {
942
+            $array['carddata'] = $this->readBlob($array['carddata']);
943
+            return $array;
944
+        }, $cards);
945
+    }
946
+
947
+    /**
948
+     * @param int $bookId
949
+     * @param string $name
950
+     * @return array
951
+     */
952
+    public function collectCardProperties($bookId, $name) {
953
+        $query = $this->db->getQueryBuilder();
954
+        $result = $query->selectDistinct('value')
955
+            ->from($this->dbCardsPropertiesTable)
956
+            ->where($query->expr()->eq('name', $query->createNamedParameter($name)))
957
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
958
+            ->execute();
959
+
960
+        $all = $result->fetchAll(PDO::FETCH_COLUMN);
961
+        $result->closeCursor();
962
+
963
+        return $all;
964
+    }
965
+
966
+    /**
967
+     * get URI from a given contact
968
+     *
969
+     * @param int $id
970
+     * @return string
971
+     */
972
+    public function getCardUri($id) {
973
+        $query = $this->db->getQueryBuilder();
974
+        $query->select('uri')->from($this->dbCardsTable)
975
+                ->where($query->expr()->eq('id', $query->createParameter('id')))
976
+                ->setParameter('id', $id);
977
+
978
+        $result = $query->execute();
979
+        $uri = $result->fetch();
980
+        $result->closeCursor();
981
+
982
+        if (!isset($uri['uri'])) {
983
+            throw new \InvalidArgumentException('Card does not exists: ' . $id);
984
+        }
985
+
986
+        return $uri['uri'];
987
+    }
988
+
989
+    /**
990
+     * return contact with the given URI
991
+     *
992
+     * @param int $addressBookId
993
+     * @param string $uri
994
+     * @returns array
995
+     */
996
+    public function getContact($addressBookId, $uri) {
997
+        $result = [];
998
+        $query = $this->db->getQueryBuilder();
999
+        $query->select('*')->from($this->dbCardsTable)
1000
+                ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1001
+                ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1002
+        $queryResult = $query->execute();
1003
+        $contact = $queryResult->fetch();
1004
+        $queryResult->closeCursor();
1005
+
1006
+        if (is_array($contact)) {
1007
+            $result = $contact;
1008
+        }
1009
+
1010
+        return $result;
1011
+    }
1012
+
1013
+    /**
1014
+     * Returns the list of people whom this address book is shared with.
1015
+     *
1016
+     * Every element in this array should have the following properties:
1017
+     *   * href - Often a mailto: address
1018
+     *   * commonName - Optional, for example a first + last name
1019
+     *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
1020
+     *   * readOnly - boolean
1021
+     *   * summary - Optional, a description for the share
1022
+     *
1023
+     * @return array
1024
+     */
1025
+    public function getShares($addressBookId) {
1026
+        return $this->sharingBackend->getShares($addressBookId);
1027
+    }
1028
+
1029
+    /**
1030
+     * update properties table
1031
+     *
1032
+     * @param int $addressBookId
1033
+     * @param string $cardUri
1034
+     * @param string $vCardSerialized
1035
+     */
1036
+    protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1037
+        $cardId = $this->getCardId($addressBookId, $cardUri);
1038
+        $vCard = $this->readCard($vCardSerialized);
1039
+
1040
+        $this->purgeProperties($addressBookId, $cardId);
1041
+
1042
+        $query = $this->db->getQueryBuilder();
1043
+        $query->insert($this->dbCardsPropertiesTable)
1044
+            ->values(
1045
+                [
1046
+                    'addressbookid' => $query->createNamedParameter($addressBookId),
1047
+                    'cardid' => $query->createNamedParameter($cardId),
1048
+                    'name' => $query->createParameter('name'),
1049
+                    'value' => $query->createParameter('value'),
1050
+                    'preferred' => $query->createParameter('preferred')
1051
+                ]
1052
+            );
1053
+
1054
+        foreach ($vCard->children() as $property) {
1055
+            if(!in_array($property->name, self::$indexProperties)) {
1056
+                continue;
1057
+            }
1058
+            $preferred = 0;
1059
+            foreach($property->parameters as $parameter) {
1060
+                if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1061
+                    $preferred = 1;
1062
+                    break;
1063
+                }
1064
+            }
1065
+            $query->setParameter('name', $property->name);
1066
+            $query->setParameter('value', mb_substr($property->getValue(), 0, 254));
1067
+            $query->setParameter('preferred', $preferred);
1068
+            $query->execute();
1069
+        }
1070
+    }
1071
+
1072
+    /**
1073
+     * read vCard data into a vCard object
1074
+     *
1075
+     * @param string $cardData
1076
+     * @return VCard
1077
+     */
1078
+    protected function readCard($cardData) {
1079
+        return  Reader::read($cardData);
1080
+    }
1081
+
1082
+    /**
1083
+     * delete all properties from a given card
1084
+     *
1085
+     * @param int $addressBookId
1086
+     * @param int $cardId
1087
+     */
1088
+    protected function purgeProperties($addressBookId, $cardId) {
1089
+        $query = $this->db->getQueryBuilder();
1090
+        $query->delete($this->dbCardsPropertiesTable)
1091
+            ->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1092
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1093
+        $query->execute();
1094
+    }
1095
+
1096
+    /**
1097
+     * get ID from a given contact
1098
+     *
1099
+     * @param int $addressBookId
1100
+     * @param string $uri
1101
+     * @return int
1102
+     */
1103
+    protected function getCardId($addressBookId, $uri) {
1104
+        $query = $this->db->getQueryBuilder();
1105
+        $query->select('id')->from($this->dbCardsTable)
1106
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1107
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1108
+
1109
+        $result = $query->execute();
1110
+        $cardIds = $result->fetch();
1111
+        $result->closeCursor();
1112
+
1113
+        if (!isset($cardIds['id'])) {
1114
+            throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1115
+        }
1116
+
1117
+        return (int)$cardIds['id'];
1118
+    }
1119
+
1120
+    /**
1121
+     * For shared address books the sharee is set in the ACL of the address book
1122
+     * @param $addressBookId
1123
+     * @param $acl
1124
+     * @return array
1125
+     */
1126
+    public function applyShareAcl($addressBookId, $acl) {
1127
+        return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1128
+    }
1129
+
1130
+    private function convertPrincipal($principalUri, $toV2) {
1131
+        if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1132
+            list(, $name) = \Sabre\Uri\split($principalUri);
1133
+            if ($toV2 === true) {
1134
+                return "principals/users/$name";
1135
+            }
1136
+            return "principals/$name";
1137
+        }
1138
+        return $principalUri;
1139
+    }
1140
+
1141
+    private function addOwnerPrincipal(&$addressbookInfo) {
1142
+        $ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1143
+        $displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1144
+        if (isset($addressbookInfo[$ownerPrincipalKey])) {
1145
+            $uri = $addressbookInfo[$ownerPrincipalKey];
1146
+        } else {
1147
+            $uri = $addressbookInfo['principaluri'];
1148
+        }
1149
+
1150
+        $principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1151
+        if (isset($principalInformation['{DAV:}displayname'])) {
1152
+            $addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1153
+        }
1154
+    }
1155
+
1156
+    /**
1157
+     * Extract UID from vcard
1158
+     *
1159
+     * @param string $cardData the vcard raw data
1160
+     * @return string the uid
1161
+     * @throws BadRequest if no UID is available
1162
+     */
1163
+    private function getUID($cardData) {
1164
+        if ($cardData != '') {
1165
+            $vCard = Reader::read($cardData);
1166
+            if ($vCard->UID) {
1167
+                $uid = $vCard->UID->getValue();
1168
+                return $uid;
1169
+            }
1170
+            // should already be handled, but just in case
1171
+            throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
1172
+        }
1173
+        // should already be handled, but just in case
1174
+        throw new BadRequest('vCard can not be empty');
1175
+    }
1176 1176
 }
Please login to merge, or discard this patch.
apps/theming/appinfo/routes.php 1 patch
Indentation   +54 added lines, -54 removed lines patch added patch discarded remove patch
@@ -28,59 +28,59 @@
 block discarded – undo
28 28
  */
29 29
 
30 30
 return ['routes' => [
31
-	[
32
-		'name' => 'Theming#updateStylesheet',
33
-		'url' => '/ajax/updateStylesheet',
34
-		'verb' => 'POST'
35
-	],
36
-	[
37
-		'name' => 'Theming#undo',
38
-		'url' => '/ajax/undoChanges',
39
-		'verb' => 'POST'
40
-	],
41
-	[
42
-		'name' => 'Theming#uploadImage',
43
-		'url' => '/ajax/uploadImage',
44
-		'verb' => 'POST'
45
-	],
46
-	[
47
-		'name' => 'Theming#getStylesheet',
48
-		'url' => '/styles',
49
-		'verb' => 'GET',
50
-	],
51
-	[
52
-		'name' => 'Theming#getImage',
53
-		'url' => '/image/{key}',
54
-		'verb' => 'GET',
55
-	],
56
-	[
57
-		'name' => 'Theming#getJavascript',
58
-		'url' => '/js/theming',
59
-		'verb' => 'GET',
60
-	],
61
-	[
62
-		'name' => 'Theming#getManifest',
63
-		'url' => '/manifest/{app}',
64
-		'verb' => 'GET',
65
-		'defaults' => ['app' => 'core']
66
-	],
67
-	[
68
-		'name'	=> 'Icon#getFavicon',
69
-		'url' => '/favicon/{app}',
70
-		'verb' => 'GET',
71
-		'defaults' => ['app' => 'core'],
72
-	],
73
-	[
74
-		'name'	=> 'Icon#getTouchIcon',
75
-		'url' => '/icon/{app}',
76
-		'verb' => 'GET',
77
-		'defaults' => ['app' => 'core'],
78
-	],
79
-	[
80
-		'name'	=> 'Icon#getThemedIcon',
81
-		'url' => '/img/{app}/{image}',
82
-		'verb' => 'GET',
83
-		'requirements' => ['image' => '.+']
84
-	],
31
+    [
32
+        'name' => 'Theming#updateStylesheet',
33
+        'url' => '/ajax/updateStylesheet',
34
+        'verb' => 'POST'
35
+    ],
36
+    [
37
+        'name' => 'Theming#undo',
38
+        'url' => '/ajax/undoChanges',
39
+        'verb' => 'POST'
40
+    ],
41
+    [
42
+        'name' => 'Theming#uploadImage',
43
+        'url' => '/ajax/uploadImage',
44
+        'verb' => 'POST'
45
+    ],
46
+    [
47
+        'name' => 'Theming#getStylesheet',
48
+        'url' => '/styles',
49
+        'verb' => 'GET',
50
+    ],
51
+    [
52
+        'name' => 'Theming#getImage',
53
+        'url' => '/image/{key}',
54
+        'verb' => 'GET',
55
+    ],
56
+    [
57
+        'name' => 'Theming#getJavascript',
58
+        'url' => '/js/theming',
59
+        'verb' => 'GET',
60
+    ],
61
+    [
62
+        'name' => 'Theming#getManifest',
63
+        'url' => '/manifest/{app}',
64
+        'verb' => 'GET',
65
+        'defaults' => ['app' => 'core']
66
+    ],
67
+    [
68
+        'name'	=> 'Icon#getFavicon',
69
+        'url' => '/favicon/{app}',
70
+        'verb' => 'GET',
71
+        'defaults' => ['app' => 'core'],
72
+    ],
73
+    [
74
+        'name'	=> 'Icon#getTouchIcon',
75
+        'url' => '/icon/{app}',
76
+        'verb' => 'GET',
77
+        'defaults' => ['app' => 'core'],
78
+    ],
79
+    [
80
+        'name'	=> 'Icon#getThemedIcon',
81
+        'url' => '/img/{app}/{image}',
82
+        'verb' => 'GET',
83
+        'requirements' => ['image' => '.+']
84
+    ],
85 85
 ]];
86 86
 
Please login to merge, or discard this patch.
apps/theming/lib/Util.php 1 patch
Indentation   +210 added lines, -210 removed lines patch added patch discarded remove patch
@@ -38,215 +38,215 @@
 block discarded – undo
38 38
 
39 39
 class Util {
40 40
 
41
-	/** @var IConfig */
42
-	private $config;
43
-
44
-	/** @var IAppManager */
45
-	private $appManager;
46
-
47
-	/** @var IAppData */
48
-	private $appData;
49
-
50
-	/**
51
-	 * Util constructor.
52
-	 *
53
-	 * @param IConfig $config
54
-	 * @param IAppManager $appManager
55
-	 * @param IAppData $appData
56
-	 */
57
-	public function __construct(IConfig $config, IAppManager $appManager, IAppData $appData) {
58
-		$this->config = $config;
59
-		$this->appManager = $appManager;
60
-		$this->appData = $appData;
61
-	}
62
-
63
-	/**
64
-	 * @param string $color rgb color value
65
-	 * @return bool
66
-	 */
67
-	public function invertTextColor($color) {
68
-		$l = $this->calculateLuma($color);
69
-		if($l>0.6) {
70
-			return true;
71
-		} else {
72
-			return false;
73
-		}
74
-	}
75
-
76
-	/**
77
-	 * get color for on-page elements:
78
-	 * theme color by default, grey if theme color is to bright
79
-	 * @param $color
80
-	 * @return string
81
-	 */
82
-	public function elementColor($color) {
83
-		$l = $this->calculateLuminance($color);
84
-		if($l>0.8) {
85
-			return '#aaaaaa';
86
-		}
87
-		return $color;
88
-	}
89
-
90
-	/**
91
-	 * @param string $color rgb color value
92
-	 * @return float
93
-	 */
94
-	public function calculateLuminance($color) {
95
-		list($red, $green, $blue) = $this->hexToRGB($color);
96
-		$compiler = new Compiler();
97
-		$hsl = $compiler->toHSL($red, $green, $blue);
98
-		return $hsl[3]/100;
99
-	}
100
-
101
-	/**
102
-	 * @param string $color rgb color value
103
-	 * @return float
104
-	 */
105
-	public function calculateLuma($color) {
106
-		list($red, $green, $blue) = $this->hexToRGB($color);
107
-		return (0.2126 * $red  + 0.7152 * $green + 0.0722 * $blue) / 255;
108
-	}
109
-
110
-	/**
111
-	 * @param string $color rgb color value
112
-	 * @return int[]
113
-	 */
114
-	public function hexToRGB($color) {
115
-		$hex = preg_replace("/[^0-9A-Fa-f]/", '', $color);
116
-		if (strlen($hex) === 3) {
117
-			$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
118
-		}
119
-		if (strlen($hex) !== 6) {
120
-			return 0;
121
-		}
122
-		return [
123
-			hexdec(substr($hex, 0, 2)),
124
-			hexdec(substr($hex, 2, 2)),
125
-			hexdec(substr($hex, 4, 2))
126
-		];
127
-	}
128
-
129
-	/**
130
-	 * @param $color
131
-	 * @return string base64 encoded radio button svg
132
-	 */
133
-	public function generateRadioButton($color) {
134
-		$radioButtonIcon = '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16">' .
135
-			'<path d="M8 1a7 7 0 0 0-7 7 7 7 0 0 0 7 7 7 7 0 0 0 7-7 7 7 0 0 0-7-7zm0 1a6 6 0 0 1 6 6 6 6 0 0 1-6 6 6 6 0 0 1-6-6 6 6 0 0 1 6-6zm0 2a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" fill="'.$color.'"/></svg>';
136
-		return base64_encode($radioButtonIcon);
137
-	}
138
-
139
-
140
-	/**
141
-	 * @param $app string app name
142
-	 * @return string|ISimpleFile path to app icon / file of logo
143
-	 */
144
-	public function getAppIcon($app) {
145
-		$app = str_replace(['\0', '/', '\\', '..'], '', $app);
146
-		try {
147
-			$appPath = $this->appManager->getAppPath($app);
148
-			$icon = $appPath . '/img/' . $app . '.svg';
149
-			if (file_exists($icon)) {
150
-				return $icon;
151
-			}
152
-			$icon = $appPath . '/img/app.svg';
153
-			if (file_exists($icon)) {
154
-				return $icon;
155
-			}
156
-		} catch (AppPathNotFoundException $e) {}
157
-
158
-		if ($this->config->getAppValue('theming', 'logoMime', '') !== '') {
159
-			$logoFile = null;
160
-			try {
161
-				$folder = $this->appData->getFolder('images');
162
-				if ($folder !== null) {
163
-					return $folder->getFile('logo');
164
-				}
165
-			} catch (NotFoundException $e) {}
166
-		}
167
-		return \OC::$SERVERROOT . '/core/img/logo/logo.svg';
168
-	}
169
-
170
-	/**
171
-	 * @param $app string app name
172
-	 * @param $image string relative path to image in app folder
173
-	 * @return string|false absolute path to image
174
-	 */
175
-	public function getAppImage($app, $image) {
176
-		$app = str_replace(['\0', '/', '\\', '..'], '', $app);
177
-		$image = str_replace(['\0', '\\', '..'], '', $image);
178
-		if ($app === "core") {
179
-			$icon = \OC::$SERVERROOT . '/core/img/' . $image;
180
-			if (file_exists($icon)) {
181
-				return $icon;
182
-			}
183
-		}
184
-
185
-		try {
186
-			$appPath = $this->appManager->getAppPath($app);
187
-		} catch (AppPathNotFoundException $e) {
188
-			return false;
189
-		}
190
-
191
-		$icon = $appPath . '/img/' . $image;
192
-		if (file_exists($icon)) {
193
-			return $icon;
194
-		}
195
-		$icon = $appPath . '/img/' . $image . '.svg';
196
-		if (file_exists($icon)) {
197
-			return $icon;
198
-		}
199
-		$icon = $appPath . '/img/' . $image . '.png';
200
-		if (file_exists($icon)) {
201
-			return $icon;
202
-		}
203
-		$icon = $appPath . '/img/' . $image . '.gif';
204
-		if (file_exists($icon)) {
205
-			return $icon;
206
-		}
207
-		$icon = $appPath . '/img/' . $image . '.jpg';
208
-		if (file_exists($icon)) {
209
-			return $icon;
210
-		}
211
-
212
-		return false;
213
-	}
214
-
215
-	/**
216
-	 * replace default color with a custom one
217
-	 *
218
-	 * @param $svg string content of a svg file
219
-	 * @param $color string color to match
220
-	 * @return string
221
-	 */
222
-	public function colorizeSvg($svg, $color) {
223
-		$svg = preg_replace('/#0082c9/i', $color, $svg);
224
-		return $svg;
225
-	}
226
-
227
-	/**
228
-	 * Check if a custom theme is set in the server configuration
229
-	 *
230
-	 * @return bool
231
-	 */
232
-	public function isAlreadyThemed() {
233
-		$theme = $this->config->getSystemValue('theme', '');
234
-		if ($theme !== '') {
235
-			return true;
236
-		}
237
-		return false;
238
-	}
239
-
240
-	public function isBackgroundThemed() {
241
-		$backgroundLogo = $this->config->getAppValue('theming', 'backgroundMime',false);
242
-
243
-		$backgroundExists = true;
244
-		try {
245
-			$this->appData->getFolder('images')->getFile('background');
246
-		} catch (\Exception $e) {
247
-			$backgroundExists = false;
248
-		}
249
-		return $backgroundLogo && $backgroundLogo !== 'backgroundColor' && $backgroundExists;
250
-	}
41
+    /** @var IConfig */
42
+    private $config;
43
+
44
+    /** @var IAppManager */
45
+    private $appManager;
46
+
47
+    /** @var IAppData */
48
+    private $appData;
49
+
50
+    /**
51
+     * Util constructor.
52
+     *
53
+     * @param IConfig $config
54
+     * @param IAppManager $appManager
55
+     * @param IAppData $appData
56
+     */
57
+    public function __construct(IConfig $config, IAppManager $appManager, IAppData $appData) {
58
+        $this->config = $config;
59
+        $this->appManager = $appManager;
60
+        $this->appData = $appData;
61
+    }
62
+
63
+    /**
64
+     * @param string $color rgb color value
65
+     * @return bool
66
+     */
67
+    public function invertTextColor($color) {
68
+        $l = $this->calculateLuma($color);
69
+        if($l>0.6) {
70
+            return true;
71
+        } else {
72
+            return false;
73
+        }
74
+    }
75
+
76
+    /**
77
+     * get color for on-page elements:
78
+     * theme color by default, grey if theme color is to bright
79
+     * @param $color
80
+     * @return string
81
+     */
82
+    public function elementColor($color) {
83
+        $l = $this->calculateLuminance($color);
84
+        if($l>0.8) {
85
+            return '#aaaaaa';
86
+        }
87
+        return $color;
88
+    }
89
+
90
+    /**
91
+     * @param string $color rgb color value
92
+     * @return float
93
+     */
94
+    public function calculateLuminance($color) {
95
+        list($red, $green, $blue) = $this->hexToRGB($color);
96
+        $compiler = new Compiler();
97
+        $hsl = $compiler->toHSL($red, $green, $blue);
98
+        return $hsl[3]/100;
99
+    }
100
+
101
+    /**
102
+     * @param string $color rgb color value
103
+     * @return float
104
+     */
105
+    public function calculateLuma($color) {
106
+        list($red, $green, $blue) = $this->hexToRGB($color);
107
+        return (0.2126 * $red  + 0.7152 * $green + 0.0722 * $blue) / 255;
108
+    }
109
+
110
+    /**
111
+     * @param string $color rgb color value
112
+     * @return int[]
113
+     */
114
+    public function hexToRGB($color) {
115
+        $hex = preg_replace("/[^0-9A-Fa-f]/", '', $color);
116
+        if (strlen($hex) === 3) {
117
+            $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
118
+        }
119
+        if (strlen($hex) !== 6) {
120
+            return 0;
121
+        }
122
+        return [
123
+            hexdec(substr($hex, 0, 2)),
124
+            hexdec(substr($hex, 2, 2)),
125
+            hexdec(substr($hex, 4, 2))
126
+        ];
127
+    }
128
+
129
+    /**
130
+     * @param $color
131
+     * @return string base64 encoded radio button svg
132
+     */
133
+    public function generateRadioButton($color) {
134
+        $radioButtonIcon = '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16">' .
135
+            '<path d="M8 1a7 7 0 0 0-7 7 7 7 0 0 0 7 7 7 7 0 0 0 7-7 7 7 0 0 0-7-7zm0 1a6 6 0 0 1 6 6 6 6 0 0 1-6 6 6 6 0 0 1-6-6 6 6 0 0 1 6-6zm0 2a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" fill="'.$color.'"/></svg>';
136
+        return base64_encode($radioButtonIcon);
137
+    }
138
+
139
+
140
+    /**
141
+     * @param $app string app name
142
+     * @return string|ISimpleFile path to app icon / file of logo
143
+     */
144
+    public function getAppIcon($app) {
145
+        $app = str_replace(['\0', '/', '\\', '..'], '', $app);
146
+        try {
147
+            $appPath = $this->appManager->getAppPath($app);
148
+            $icon = $appPath . '/img/' . $app . '.svg';
149
+            if (file_exists($icon)) {
150
+                return $icon;
151
+            }
152
+            $icon = $appPath . '/img/app.svg';
153
+            if (file_exists($icon)) {
154
+                return $icon;
155
+            }
156
+        } catch (AppPathNotFoundException $e) {}
157
+
158
+        if ($this->config->getAppValue('theming', 'logoMime', '') !== '') {
159
+            $logoFile = null;
160
+            try {
161
+                $folder = $this->appData->getFolder('images');
162
+                if ($folder !== null) {
163
+                    return $folder->getFile('logo');
164
+                }
165
+            } catch (NotFoundException $e) {}
166
+        }
167
+        return \OC::$SERVERROOT . '/core/img/logo/logo.svg';
168
+    }
169
+
170
+    /**
171
+     * @param $app string app name
172
+     * @param $image string relative path to image in app folder
173
+     * @return string|false absolute path to image
174
+     */
175
+    public function getAppImage($app, $image) {
176
+        $app = str_replace(['\0', '/', '\\', '..'], '', $app);
177
+        $image = str_replace(['\0', '\\', '..'], '', $image);
178
+        if ($app === "core") {
179
+            $icon = \OC::$SERVERROOT . '/core/img/' . $image;
180
+            if (file_exists($icon)) {
181
+                return $icon;
182
+            }
183
+        }
184
+
185
+        try {
186
+            $appPath = $this->appManager->getAppPath($app);
187
+        } catch (AppPathNotFoundException $e) {
188
+            return false;
189
+        }
190
+
191
+        $icon = $appPath . '/img/' . $image;
192
+        if (file_exists($icon)) {
193
+            return $icon;
194
+        }
195
+        $icon = $appPath . '/img/' . $image . '.svg';
196
+        if (file_exists($icon)) {
197
+            return $icon;
198
+        }
199
+        $icon = $appPath . '/img/' . $image . '.png';
200
+        if (file_exists($icon)) {
201
+            return $icon;
202
+        }
203
+        $icon = $appPath . '/img/' . $image . '.gif';
204
+        if (file_exists($icon)) {
205
+            return $icon;
206
+        }
207
+        $icon = $appPath . '/img/' . $image . '.jpg';
208
+        if (file_exists($icon)) {
209
+            return $icon;
210
+        }
211
+
212
+        return false;
213
+    }
214
+
215
+    /**
216
+     * replace default color with a custom one
217
+     *
218
+     * @param $svg string content of a svg file
219
+     * @param $color string color to match
220
+     * @return string
221
+     */
222
+    public function colorizeSvg($svg, $color) {
223
+        $svg = preg_replace('/#0082c9/i', $color, $svg);
224
+        return $svg;
225
+    }
226
+
227
+    /**
228
+     * Check if a custom theme is set in the server configuration
229
+     *
230
+     * @return bool
231
+     */
232
+    public function isAlreadyThemed() {
233
+        $theme = $this->config->getSystemValue('theme', '');
234
+        if ($theme !== '') {
235
+            return true;
236
+        }
237
+        return false;
238
+    }
239
+
240
+    public function isBackgroundThemed() {
241
+        $backgroundLogo = $this->config->getAppValue('theming', 'backgroundMime',false);
242
+
243
+        $backgroundExists = true;
244
+        try {
245
+            $this->appData->getFolder('images')->getFile('background');
246
+        } catch (\Exception $e) {
247
+            $backgroundExists = false;
248
+        }
249
+        return $backgroundLogo && $backgroundLogo !== 'backgroundColor' && $backgroundExists;
250
+    }
251 251
 
252 252
 }
Please login to merge, or discard this patch.
apps/encryption/templates/altmail.php 1 patch
Indentation   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -4,8 +4,8 @@
 block discarded – undo
4 4
 
5 5
 print_unescaped($l->t("Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n", [$_['password']]));
6 6
 if ( isset($_['expiration']) ) {
7
-	print_unescaped($l->t("The share will expire on %s.", [$_['expiration']]));
8
-	print_unescaped("\n\n");
7
+    print_unescaped($l->t("The share will expire on %s.", [$_['expiration']]));
8
+    print_unescaped("\n\n");
9 9
 }
10 10
 // TRANSLATORS term at the end of a mail
11 11
 p($l->t("Cheers!"));
Please login to merge, or discard this patch.
apps/encryption/lib/Crypto/Encryption.php 1 patch
Indentation   +553 added lines, -553 removed lines patch added patch discarded remove patch
@@ -46,557 +46,557 @@
 block discarded – undo
46 46
 
47 47
 class Encryption implements IEncryptionModule {
48 48
 
49
-	const ID = 'OC_DEFAULT_MODULE';
50
-	const DISPLAY_NAME = 'Default encryption module';
51
-
52
-	/**
53
-	 * @var Crypt
54
-	 */
55
-	private $crypt;
56
-
57
-	/** @var string */
58
-	private $cipher;
59
-
60
-	/** @var string */
61
-	private $path;
62
-
63
-	/** @var string */
64
-	private $user;
65
-
66
-	/** @var  array */
67
-	private $owner;
68
-
69
-	/** @var string */
70
-	private $fileKey;
71
-
72
-	/** @var string */
73
-	private $writeCache;
74
-
75
-	/** @var KeyManager */
76
-	private $keyManager;
77
-
78
-	/** @var array */
79
-	private $accessList;
80
-
81
-	/** @var boolean */
82
-	private $isWriteOperation;
83
-
84
-	/** @var Util */
85
-	private $util;
86
-
87
-	/** @var  Session */
88
-	private $session;
89
-
90
-	/** @var  ILogger */
91
-	private $logger;
92
-
93
-	/** @var IL10N */
94
-	private $l;
95
-
96
-	/** @var EncryptAll */
97
-	private $encryptAll;
98
-
99
-	/** @var  bool */
100
-	private $useMasterPassword;
101
-
102
-	/** @var DecryptAll  */
103
-	private $decryptAll;
104
-
105
-	/** @var int unencrypted block size if block contains signature */
106
-	private $unencryptedBlockSizeSigned = 6072;
107
-
108
-	/** @var int unencrypted block size */
109
-	private $unencryptedBlockSize = 6126;
110
-
111
-	/** @var int Current version of the file */
112
-	private $version = 0;
113
-
114
-	/** @var array remember encryption signature version */
115
-	private static $rememberVersion = [];
116
-
117
-
118
-	/**
119
-	 *
120
-	 * @param Crypt $crypt
121
-	 * @param KeyManager $keyManager
122
-	 * @param Util $util
123
-	 * @param Session $session
124
-	 * @param EncryptAll $encryptAll
125
-	 * @param DecryptAll $decryptAll
126
-	 * @param ILogger $logger
127
-	 * @param IL10N $il10n
128
-	 */
129
-	public function __construct(Crypt $crypt,
130
-								KeyManager $keyManager,
131
-								Util $util,
132
-								Session $session,
133
-								EncryptAll $encryptAll,
134
-								DecryptAll $decryptAll,
135
-								ILogger $logger,
136
-								IL10N $il10n) {
137
-		$this->crypt = $crypt;
138
-		$this->keyManager = $keyManager;
139
-		$this->util = $util;
140
-		$this->session = $session;
141
-		$this->encryptAll = $encryptAll;
142
-		$this->decryptAll = $decryptAll;
143
-		$this->logger = $logger;
144
-		$this->l = $il10n;
145
-		$this->owner = [];
146
-		$this->useMasterPassword = $util->isMasterKeyEnabled();
147
-	}
148
-
149
-	/**
150
-	 * @return string defining the technical unique id
151
-	 */
152
-	public function getId() {
153
-		return self::ID;
154
-	}
155
-
156
-	/**
157
-	 * In comparison to getKey() this function returns a human readable (maybe translated) name
158
-	 *
159
-	 * @return string
160
-	 */
161
-	public function getDisplayName() {
162
-		return self::DISPLAY_NAME;
163
-	}
164
-
165
-	/**
166
-	 * start receiving chunks from a file. This is the place where you can
167
-	 * perform some initial step before starting encrypting/decrypting the
168
-	 * chunks
169
-	 *
170
-	 * @param string $path to the file
171
-	 * @param string $user who read/write the file
172
-	 * @param string $mode php stream open mode
173
-	 * @param array $header contains the header data read from the file
174
-	 * @param array $accessList who has access to the file contains the key 'users' and 'public'
175
-	 *
176
-	 * @return array $header contain data as key-value pairs which should be
177
-	 *                       written to the header, in case of a write operation
178
-	 *                       or if no additional data is needed return a empty array
179
-	 */
180
-	public function begin($path, $user, $mode, array $header, array $accessList) {
181
-		$this->path = $this->getPathToRealFile($path);
182
-		$this->accessList = $accessList;
183
-		$this->user = $user;
184
-		$this->isWriteOperation = false;
185
-		$this->writeCache = '';
186
-
187
-		if($this->session->isReady() === false) {
188
-			// if the master key is enabled we can initialize encryption
189
-			// with a empty password and user name
190
-			if ($this->util->isMasterKeyEnabled()) {
191
-				$this->keyManager->init('', '');
192
-			}
193
-		}
194
-
195
-		if ($this->session->decryptAllModeActivated()) {
196
-			$encryptedFileKey = $this->keyManager->getEncryptedFileKey($this->path);
197
-			$shareKey = $this->keyManager->getShareKey($this->path, $this->session->getDecryptAllUid());
198
-			$this->fileKey = $this->crypt->multiKeyDecrypt($encryptedFileKey,
199
-				$shareKey,
200
-				$this->session->getDecryptAllKey());
201
-		} else {
202
-			$this->fileKey = $this->keyManager->getFileKey($this->path, $this->user);
203
-		}
204
-
205
-		// always use the version from the original file, also part files
206
-		// need to have a correct version number if they get moved over to the
207
-		// final location
208
-		$this->version = (int)$this->keyManager->getVersion($this->stripPartFileExtension($path), new View());
209
-
210
-		if (
211
-			$mode === 'w'
212
-			|| $mode === 'w+'
213
-			|| $mode === 'wb'
214
-			|| $mode === 'wb+'
215
-		) {
216
-			$this->isWriteOperation = true;
217
-			if (empty($this->fileKey)) {
218
-				$this->fileKey = $this->crypt->generateFileKey();
219
-			}
220
-		} else {
221
-			// if we read a part file we need to increase the version by 1
222
-			// because the version number was also increased by writing
223
-			// the part file
224
-			if(Scanner::isPartialFile($path)) {
225
-				$this->version = $this->version + 1;
226
-			}
227
-		}
228
-
229
-		if ($this->isWriteOperation) {
230
-			$this->cipher = $this->crypt->getCipher();
231
-		} elseif (isset($header['cipher'])) {
232
-			$this->cipher = $header['cipher'];
233
-		} else {
234
-			// if we read a file without a header we fall-back to the legacy cipher
235
-			// which was used in <=oC6
236
-			$this->cipher = $this->crypt->getLegacyCipher();
237
-		}
238
-
239
-		return ['cipher' => $this->cipher, 'signed' => 'true'];
240
-	}
241
-
242
-	/**
243
-	 * last chunk received. This is the place where you can perform some final
244
-	 * operation and return some remaining data if something is left in your
245
-	 * buffer.
246
-	 *
247
-	 * @param string $path to the file
248
-	 * @param int $position
249
-	 * @return string remained data which should be written to the file in case
250
-	 *                of a write operation
251
-	 * @throws PublicKeyMissingException
252
-	 * @throws \Exception
253
-	 * @throws \OCA\Encryption\Exceptions\MultiKeyEncryptException
254
-	 */
255
-	public function end($path, $position = 0) {
256
-		$result = '';
257
-		if ($this->isWriteOperation) {
258
-			// in case of a part file we remember the new signature versions
259
-			// the version will be set later on update.
260
-			// This way we make sure that other apps listening to the pre-hooks
261
-			// still get the old version which should be the correct value for them
262
-			if (Scanner::isPartialFile($path)) {
263
-				self::$rememberVersion[$this->stripPartFileExtension($path)] = $this->version + 1;
264
-			}
265
-			if (!empty($this->writeCache)) {
266
-				$result = $this->crypt->symmetricEncryptFileContent($this->writeCache, $this->fileKey, $this->version + 1, $position);
267
-				$this->writeCache = '';
268
-			}
269
-			$publicKeys = [];
270
-			if ($this->useMasterPassword === true) {
271
-				$publicKeys[$this->keyManager->getMasterKeyId()] = $this->keyManager->getPublicMasterKey();
272
-			} else {
273
-				foreach ($this->accessList['users'] as $uid) {
274
-					try {
275
-						$publicKeys[$uid] = $this->keyManager->getPublicKey($uid);
276
-					} catch (PublicKeyMissingException $e) {
277
-						$this->logger->warning(
278
-							'no public key found for user "{uid}", user will not be able to read the file',
279
-							['app' => 'encryption', 'uid' => $uid]
280
-						);
281
-						// if the public key of the owner is missing we should fail
282
-						if ($uid === $this->user) {
283
-							throw $e;
284
-						}
285
-					}
286
-				}
287
-			}
288
-
289
-			$publicKeys = $this->keyManager->addSystemKeys($this->accessList, $publicKeys, $this->getOwner($path));
290
-			$encryptedKeyfiles = $this->crypt->multiKeyEncrypt($this->fileKey, $publicKeys);
291
-			$this->keyManager->setAllFileKeys($this->path, $encryptedKeyfiles);
292
-		}
293
-		return $result;
294
-	}
295
-
296
-
297
-
298
-	/**
299
-	 * encrypt data
300
-	 *
301
-	 * @param string $data you want to encrypt
302
-	 * @param int $position
303
-	 * @return string encrypted data
304
-	 */
305
-	public function encrypt($data, $position = 0) {
306
-		// If extra data is left over from the last round, make sure it
307
-		// is integrated into the next block
308
-		if ($this->writeCache) {
309
-
310
-			// Concat writeCache to start of $data
311
-			$data = $this->writeCache . $data;
312
-
313
-			// Clear the write cache, ready for reuse - it has been
314
-			// flushed and its old contents processed
315
-			$this->writeCache = '';
316
-
317
-		}
318
-
319
-		$encrypted = '';
320
-		// While there still remains some data to be processed & written
321
-		while (strlen($data) > 0) {
322
-
323
-			// Remaining length for this iteration, not of the
324
-			// entire file (may be greater than 8192 bytes)
325
-			$remainingLength = strlen($data);
326
-
327
-			// If data remaining to be written is less than the
328
-			// size of 1 6126 byte block
329
-			if ($remainingLength < $this->unencryptedBlockSizeSigned) {
330
-
331
-				// Set writeCache to contents of $data
332
-				// The writeCache will be carried over to the
333
-				// next write round, and added to the start of
334
-				// $data to ensure that written blocks are
335
-				// always the correct length. If there is still
336
-				// data in writeCache after the writing round
337
-				// has finished, then the data will be written
338
-				// to disk by $this->flush().
339
-				$this->writeCache = $data;
340
-
341
-				// Clear $data ready for next round
342
-				$data = '';
343
-
344
-			} else {
345
-
346
-				// Read the chunk from the start of $data
347
-				$chunk = substr($data, 0, $this->unencryptedBlockSizeSigned);
348
-
349
-				$encrypted .= $this->crypt->symmetricEncryptFileContent($chunk, $this->fileKey, $this->version + 1, $position);
350
-
351
-				// Remove the chunk we just processed from
352
-				// $data, leaving only unprocessed data in $data
353
-				// var, for handling on the next round
354
-				$data = substr($data, $this->unencryptedBlockSizeSigned);
355
-
356
-			}
357
-
358
-		}
359
-
360
-		return $encrypted;
361
-	}
362
-
363
-	/**
364
-	 * decrypt data
365
-	 *
366
-	 * @param string $data you want to decrypt
367
-	 * @param int $position
368
-	 * @return string decrypted data
369
-	 * @throws DecryptionFailedException
370
-	 */
371
-	public function decrypt($data, $position = 0) {
372
-		if (empty($this->fileKey)) {
373
-			$msg = 'Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you.';
374
-			$hint = $this->l->t('Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you.');
375
-			$this->logger->error($msg);
376
-
377
-			throw new DecryptionFailedException($msg, $hint);
378
-		}
379
-
380
-		return $this->crypt->symmetricDecryptFileContent($data, $this->fileKey, $this->cipher, $this->version, $position);
381
-	}
382
-
383
-	/**
384
-	 * update encrypted file, e.g. give additional users access to the file
385
-	 *
386
-	 * @param string $path path to the file which should be updated
387
-	 * @param string $uid of the user who performs the operation
388
-	 * @param array $accessList who has access to the file contains the key 'users' and 'public'
389
-	 * @return boolean
390
-	 */
391
-	public function update($path, $uid, array $accessList) {
392
-
393
-		if (empty($accessList)) {
394
-			if (isset(self::$rememberVersion[$path])) {
395
-				$this->keyManager->setVersion($path, self::$rememberVersion[$path], new View());
396
-				unset(self::$rememberVersion[$path]);
397
-			}
398
-			return;
399
-		}
400
-
401
-		$fileKey = $this->keyManager->getFileKey($path, $uid);
402
-
403
-		if (!empty($fileKey)) {
404
-
405
-			$publicKeys = [];
406
-			if ($this->useMasterPassword === true) {
407
-				$publicKeys[$this->keyManager->getMasterKeyId()] = $this->keyManager->getPublicMasterKey();
408
-			} else {
409
-				foreach ($accessList['users'] as $user) {
410
-					try {
411
-						$publicKeys[$user] = $this->keyManager->getPublicKey($user);
412
-					} catch (PublicKeyMissingException $e) {
413
-						$this->logger->warning('Could not encrypt file for ' . $user . ': ' . $e->getMessage());
414
-					}
415
-				}
416
-			}
417
-
418
-			$publicKeys = $this->keyManager->addSystemKeys($accessList, $publicKeys, $this->getOwner($path));
419
-
420
-			$encryptedFileKey = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys);
421
-
422
-			$this->keyManager->deleteAllFileKeys($path);
423
-
424
-			$this->keyManager->setAllFileKeys($path, $encryptedFileKey);
425
-
426
-		} else {
427
-			$this->logger->debug('no file key found, we assume that the file "{file}" is not encrypted',
428
-				['file' => $path, 'app' => 'encryption']);
429
-
430
-			return false;
431
-		}
432
-
433
-		return true;
434
-	}
435
-
436
-	/**
437
-	 * should the file be encrypted or not
438
-	 *
439
-	 * @param string $path
440
-	 * @return boolean
441
-	 */
442
-	public function shouldEncrypt($path) {
443
-		if ($this->util->shouldEncryptHomeStorage() === false) {
444
-			$storage = $this->util->getStorage($path);
445
-			if ($storage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
446
-				return false;
447
-			}
448
-		}
449
-		$parts = explode('/', $path);
450
-		if (count($parts) < 4) {
451
-			return false;
452
-		}
453
-
454
-		if ($parts[2] === 'files') {
455
-			return true;
456
-		}
457
-		if ($parts[2] === 'files_versions') {
458
-			return true;
459
-		}
460
-		if ($parts[2] === 'files_trashbin') {
461
-			return true;
462
-		}
463
-
464
-		return false;
465
-	}
466
-
467
-	/**
468
-	 * get size of the unencrypted payload per block.
469
-	 * Nextcloud read/write files with a block size of 8192 byte
470
-	 *
471
-	 * @param bool $signed
472
-	 * @return int
473
-	 */
474
-	public function getUnencryptedBlockSize($signed = false) {
475
-		if ($signed === false) {
476
-			return $this->unencryptedBlockSize;
477
-		}
478
-
479
-		return $this->unencryptedBlockSizeSigned;
480
-	}
481
-
482
-	/**
483
-	 * check if the encryption module is able to read the file,
484
-	 * e.g. if all encryption keys exists
485
-	 *
486
-	 * @param string $path
487
-	 * @param string $uid user for whom we want to check if he can read the file
488
-	 * @return bool
489
-	 * @throws DecryptionFailedException
490
-	 */
491
-	public function isReadable($path, $uid) {
492
-		$fileKey = $this->keyManager->getFileKey($path, $uid);
493
-		if (empty($fileKey)) {
494
-			$owner = $this->util->getOwner($path);
495
-			if ($owner !== $uid) {
496
-				// if it is a shared file we throw a exception with a useful
497
-				// error message because in this case it means that the file was
498
-				// shared with the user at a point where the user didn't had a
499
-				// valid private/public key
500
-				$msg = 'Encryption module "' . $this->getDisplayName() .
501
-					'" is not able to read ' . $path;
502
-				$hint = $this->l->t('Can not read this file, probably this is a shared file. Please ask the file owner to reshare the file with you.');
503
-				$this->logger->warning($msg);
504
-				throw new DecryptionFailedException($msg, $hint);
505
-			}
506
-			return false;
507
-		}
508
-
509
-		return true;
510
-	}
511
-
512
-	/**
513
-	 * Initial encryption of all files
514
-	 *
515
-	 * @param InputInterface $input
516
-	 * @param OutputInterface $output write some status information to the terminal during encryption
517
-	 */
518
-	public function encryptAll(InputInterface $input, OutputInterface $output) {
519
-		$this->encryptAll->encryptAll($input, $output);
520
-	}
521
-
522
-	/**
523
-	 * prepare module to perform decrypt all operation
524
-	 *
525
-	 * @param InputInterface $input
526
-	 * @param OutputInterface $output
527
-	 * @param string $user
528
-	 * @return bool
529
-	 */
530
-	public function prepareDecryptAll(InputInterface $input, OutputInterface $output, $user = '') {
531
-		return $this->decryptAll->prepare($input, $output, $user);
532
-	}
533
-
534
-
535
-	/**
536
-	 * @param string $path
537
-	 * @return string
538
-	 */
539
-	protected function getPathToRealFile($path) {
540
-		$realPath = $path;
541
-		$parts = explode('/', $path);
542
-		if ($parts[2] === 'files_versions') {
543
-			$realPath = '/' . $parts[1] . '/files/' . implode('/', array_slice($parts, 3));
544
-			$length = strrpos($realPath, '.');
545
-			$realPath = substr($realPath, 0, $length);
546
-		}
547
-
548
-		return $realPath;
549
-	}
550
-
551
-	/**
552
-	 * remove .part file extension and the ocTransferId from the file to get the
553
-	 * original file name
554
-	 *
555
-	 * @param string $path
556
-	 * @return string
557
-	 */
558
-	protected function stripPartFileExtension($path) {
559
-		if (pathinfo($path, PATHINFO_EXTENSION) === 'part') {
560
-			$pos = strrpos($path, '.', -6);
561
-			$path = substr($path, 0, $pos);
562
-		}
563
-
564
-		return $path;
565
-	}
566
-
567
-	/**
568
-	 * get owner of a file
569
-	 *
570
-	 * @param string $path
571
-	 * @return string
572
-	 */
573
-	protected function getOwner($path) {
574
-		if (!isset($this->owner[$path])) {
575
-			$this->owner[$path] = $this->util->getOwner($path);
576
-		}
577
-		return $this->owner[$path];
578
-	}
579
-
580
-	/**
581
-	 * Check if the module is ready to be used by that specific user.
582
-	 * In case a module is not ready - because e.g. key pairs have not been generated
583
-	 * upon login this method can return false before any operation starts and might
584
-	 * cause issues during operations.
585
-	 *
586
-	 * @param string $user
587
-	 * @return boolean
588
-	 * @since 9.1.0
589
-	 */
590
-	public function isReadyForUser($user) {
591
-		return $this->keyManager->userHasKeys($user);
592
-	}
593
-
594
-	/**
595
-	 * We only need a detailed access list if the master key is not enabled
596
-	 *
597
-	 * @return bool
598
-	 */
599
-	public function needDetailedAccessList() {
600
-		return !$this->util->isMasterKeyEnabled();
601
-	}
49
+    const ID = 'OC_DEFAULT_MODULE';
50
+    const DISPLAY_NAME = 'Default encryption module';
51
+
52
+    /**
53
+     * @var Crypt
54
+     */
55
+    private $crypt;
56
+
57
+    /** @var string */
58
+    private $cipher;
59
+
60
+    /** @var string */
61
+    private $path;
62
+
63
+    /** @var string */
64
+    private $user;
65
+
66
+    /** @var  array */
67
+    private $owner;
68
+
69
+    /** @var string */
70
+    private $fileKey;
71
+
72
+    /** @var string */
73
+    private $writeCache;
74
+
75
+    /** @var KeyManager */
76
+    private $keyManager;
77
+
78
+    /** @var array */
79
+    private $accessList;
80
+
81
+    /** @var boolean */
82
+    private $isWriteOperation;
83
+
84
+    /** @var Util */
85
+    private $util;
86
+
87
+    /** @var  Session */
88
+    private $session;
89
+
90
+    /** @var  ILogger */
91
+    private $logger;
92
+
93
+    /** @var IL10N */
94
+    private $l;
95
+
96
+    /** @var EncryptAll */
97
+    private $encryptAll;
98
+
99
+    /** @var  bool */
100
+    private $useMasterPassword;
101
+
102
+    /** @var DecryptAll  */
103
+    private $decryptAll;
104
+
105
+    /** @var int unencrypted block size if block contains signature */
106
+    private $unencryptedBlockSizeSigned = 6072;
107
+
108
+    /** @var int unencrypted block size */
109
+    private $unencryptedBlockSize = 6126;
110
+
111
+    /** @var int Current version of the file */
112
+    private $version = 0;
113
+
114
+    /** @var array remember encryption signature version */
115
+    private static $rememberVersion = [];
116
+
117
+
118
+    /**
119
+     *
120
+     * @param Crypt $crypt
121
+     * @param KeyManager $keyManager
122
+     * @param Util $util
123
+     * @param Session $session
124
+     * @param EncryptAll $encryptAll
125
+     * @param DecryptAll $decryptAll
126
+     * @param ILogger $logger
127
+     * @param IL10N $il10n
128
+     */
129
+    public function __construct(Crypt $crypt,
130
+                                KeyManager $keyManager,
131
+                                Util $util,
132
+                                Session $session,
133
+                                EncryptAll $encryptAll,
134
+                                DecryptAll $decryptAll,
135
+                                ILogger $logger,
136
+                                IL10N $il10n) {
137
+        $this->crypt = $crypt;
138
+        $this->keyManager = $keyManager;
139
+        $this->util = $util;
140
+        $this->session = $session;
141
+        $this->encryptAll = $encryptAll;
142
+        $this->decryptAll = $decryptAll;
143
+        $this->logger = $logger;
144
+        $this->l = $il10n;
145
+        $this->owner = [];
146
+        $this->useMasterPassword = $util->isMasterKeyEnabled();
147
+    }
148
+
149
+    /**
150
+     * @return string defining the technical unique id
151
+     */
152
+    public function getId() {
153
+        return self::ID;
154
+    }
155
+
156
+    /**
157
+     * In comparison to getKey() this function returns a human readable (maybe translated) name
158
+     *
159
+     * @return string
160
+     */
161
+    public function getDisplayName() {
162
+        return self::DISPLAY_NAME;
163
+    }
164
+
165
+    /**
166
+     * start receiving chunks from a file. This is the place where you can
167
+     * perform some initial step before starting encrypting/decrypting the
168
+     * chunks
169
+     *
170
+     * @param string $path to the file
171
+     * @param string $user who read/write the file
172
+     * @param string $mode php stream open mode
173
+     * @param array $header contains the header data read from the file
174
+     * @param array $accessList who has access to the file contains the key 'users' and 'public'
175
+     *
176
+     * @return array $header contain data as key-value pairs which should be
177
+     *                       written to the header, in case of a write operation
178
+     *                       or if no additional data is needed return a empty array
179
+     */
180
+    public function begin($path, $user, $mode, array $header, array $accessList) {
181
+        $this->path = $this->getPathToRealFile($path);
182
+        $this->accessList = $accessList;
183
+        $this->user = $user;
184
+        $this->isWriteOperation = false;
185
+        $this->writeCache = '';
186
+
187
+        if($this->session->isReady() === false) {
188
+            // if the master key is enabled we can initialize encryption
189
+            // with a empty password and user name
190
+            if ($this->util->isMasterKeyEnabled()) {
191
+                $this->keyManager->init('', '');
192
+            }
193
+        }
194
+
195
+        if ($this->session->decryptAllModeActivated()) {
196
+            $encryptedFileKey = $this->keyManager->getEncryptedFileKey($this->path);
197
+            $shareKey = $this->keyManager->getShareKey($this->path, $this->session->getDecryptAllUid());
198
+            $this->fileKey = $this->crypt->multiKeyDecrypt($encryptedFileKey,
199
+                $shareKey,
200
+                $this->session->getDecryptAllKey());
201
+        } else {
202
+            $this->fileKey = $this->keyManager->getFileKey($this->path, $this->user);
203
+        }
204
+
205
+        // always use the version from the original file, also part files
206
+        // need to have a correct version number if they get moved over to the
207
+        // final location
208
+        $this->version = (int)$this->keyManager->getVersion($this->stripPartFileExtension($path), new View());
209
+
210
+        if (
211
+            $mode === 'w'
212
+            || $mode === 'w+'
213
+            || $mode === 'wb'
214
+            || $mode === 'wb+'
215
+        ) {
216
+            $this->isWriteOperation = true;
217
+            if (empty($this->fileKey)) {
218
+                $this->fileKey = $this->crypt->generateFileKey();
219
+            }
220
+        } else {
221
+            // if we read a part file we need to increase the version by 1
222
+            // because the version number was also increased by writing
223
+            // the part file
224
+            if(Scanner::isPartialFile($path)) {
225
+                $this->version = $this->version + 1;
226
+            }
227
+        }
228
+
229
+        if ($this->isWriteOperation) {
230
+            $this->cipher = $this->crypt->getCipher();
231
+        } elseif (isset($header['cipher'])) {
232
+            $this->cipher = $header['cipher'];
233
+        } else {
234
+            // if we read a file without a header we fall-back to the legacy cipher
235
+            // which was used in <=oC6
236
+            $this->cipher = $this->crypt->getLegacyCipher();
237
+        }
238
+
239
+        return ['cipher' => $this->cipher, 'signed' => 'true'];
240
+    }
241
+
242
+    /**
243
+     * last chunk received. This is the place where you can perform some final
244
+     * operation and return some remaining data if something is left in your
245
+     * buffer.
246
+     *
247
+     * @param string $path to the file
248
+     * @param int $position
249
+     * @return string remained data which should be written to the file in case
250
+     *                of a write operation
251
+     * @throws PublicKeyMissingException
252
+     * @throws \Exception
253
+     * @throws \OCA\Encryption\Exceptions\MultiKeyEncryptException
254
+     */
255
+    public function end($path, $position = 0) {
256
+        $result = '';
257
+        if ($this->isWriteOperation) {
258
+            // in case of a part file we remember the new signature versions
259
+            // the version will be set later on update.
260
+            // This way we make sure that other apps listening to the pre-hooks
261
+            // still get the old version which should be the correct value for them
262
+            if (Scanner::isPartialFile($path)) {
263
+                self::$rememberVersion[$this->stripPartFileExtension($path)] = $this->version + 1;
264
+            }
265
+            if (!empty($this->writeCache)) {
266
+                $result = $this->crypt->symmetricEncryptFileContent($this->writeCache, $this->fileKey, $this->version + 1, $position);
267
+                $this->writeCache = '';
268
+            }
269
+            $publicKeys = [];
270
+            if ($this->useMasterPassword === true) {
271
+                $publicKeys[$this->keyManager->getMasterKeyId()] = $this->keyManager->getPublicMasterKey();
272
+            } else {
273
+                foreach ($this->accessList['users'] as $uid) {
274
+                    try {
275
+                        $publicKeys[$uid] = $this->keyManager->getPublicKey($uid);
276
+                    } catch (PublicKeyMissingException $e) {
277
+                        $this->logger->warning(
278
+                            'no public key found for user "{uid}", user will not be able to read the file',
279
+                            ['app' => 'encryption', 'uid' => $uid]
280
+                        );
281
+                        // if the public key of the owner is missing we should fail
282
+                        if ($uid === $this->user) {
283
+                            throw $e;
284
+                        }
285
+                    }
286
+                }
287
+            }
288
+
289
+            $publicKeys = $this->keyManager->addSystemKeys($this->accessList, $publicKeys, $this->getOwner($path));
290
+            $encryptedKeyfiles = $this->crypt->multiKeyEncrypt($this->fileKey, $publicKeys);
291
+            $this->keyManager->setAllFileKeys($this->path, $encryptedKeyfiles);
292
+        }
293
+        return $result;
294
+    }
295
+
296
+
297
+
298
+    /**
299
+     * encrypt data
300
+     *
301
+     * @param string $data you want to encrypt
302
+     * @param int $position
303
+     * @return string encrypted data
304
+     */
305
+    public function encrypt($data, $position = 0) {
306
+        // If extra data is left over from the last round, make sure it
307
+        // is integrated into the next block
308
+        if ($this->writeCache) {
309
+
310
+            // Concat writeCache to start of $data
311
+            $data = $this->writeCache . $data;
312
+
313
+            // Clear the write cache, ready for reuse - it has been
314
+            // flushed and its old contents processed
315
+            $this->writeCache = '';
316
+
317
+        }
318
+
319
+        $encrypted = '';
320
+        // While there still remains some data to be processed & written
321
+        while (strlen($data) > 0) {
322
+
323
+            // Remaining length for this iteration, not of the
324
+            // entire file (may be greater than 8192 bytes)
325
+            $remainingLength = strlen($data);
326
+
327
+            // If data remaining to be written is less than the
328
+            // size of 1 6126 byte block
329
+            if ($remainingLength < $this->unencryptedBlockSizeSigned) {
330
+
331
+                // Set writeCache to contents of $data
332
+                // The writeCache will be carried over to the
333
+                // next write round, and added to the start of
334
+                // $data to ensure that written blocks are
335
+                // always the correct length. If there is still
336
+                // data in writeCache after the writing round
337
+                // has finished, then the data will be written
338
+                // to disk by $this->flush().
339
+                $this->writeCache = $data;
340
+
341
+                // Clear $data ready for next round
342
+                $data = '';
343
+
344
+            } else {
345
+
346
+                // Read the chunk from the start of $data
347
+                $chunk = substr($data, 0, $this->unencryptedBlockSizeSigned);
348
+
349
+                $encrypted .= $this->crypt->symmetricEncryptFileContent($chunk, $this->fileKey, $this->version + 1, $position);
350
+
351
+                // Remove the chunk we just processed from
352
+                // $data, leaving only unprocessed data in $data
353
+                // var, for handling on the next round
354
+                $data = substr($data, $this->unencryptedBlockSizeSigned);
355
+
356
+            }
357
+
358
+        }
359
+
360
+        return $encrypted;
361
+    }
362
+
363
+    /**
364
+     * decrypt data
365
+     *
366
+     * @param string $data you want to decrypt
367
+     * @param int $position
368
+     * @return string decrypted data
369
+     * @throws DecryptionFailedException
370
+     */
371
+    public function decrypt($data, $position = 0) {
372
+        if (empty($this->fileKey)) {
373
+            $msg = 'Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you.';
374
+            $hint = $this->l->t('Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you.');
375
+            $this->logger->error($msg);
376
+
377
+            throw new DecryptionFailedException($msg, $hint);
378
+        }
379
+
380
+        return $this->crypt->symmetricDecryptFileContent($data, $this->fileKey, $this->cipher, $this->version, $position);
381
+    }
382
+
383
+    /**
384
+     * update encrypted file, e.g. give additional users access to the file
385
+     *
386
+     * @param string $path path to the file which should be updated
387
+     * @param string $uid of the user who performs the operation
388
+     * @param array $accessList who has access to the file contains the key 'users' and 'public'
389
+     * @return boolean
390
+     */
391
+    public function update($path, $uid, array $accessList) {
392
+
393
+        if (empty($accessList)) {
394
+            if (isset(self::$rememberVersion[$path])) {
395
+                $this->keyManager->setVersion($path, self::$rememberVersion[$path], new View());
396
+                unset(self::$rememberVersion[$path]);
397
+            }
398
+            return;
399
+        }
400
+
401
+        $fileKey = $this->keyManager->getFileKey($path, $uid);
402
+
403
+        if (!empty($fileKey)) {
404
+
405
+            $publicKeys = [];
406
+            if ($this->useMasterPassword === true) {
407
+                $publicKeys[$this->keyManager->getMasterKeyId()] = $this->keyManager->getPublicMasterKey();
408
+            } else {
409
+                foreach ($accessList['users'] as $user) {
410
+                    try {
411
+                        $publicKeys[$user] = $this->keyManager->getPublicKey($user);
412
+                    } catch (PublicKeyMissingException $e) {
413
+                        $this->logger->warning('Could not encrypt file for ' . $user . ': ' . $e->getMessage());
414
+                    }
415
+                }
416
+            }
417
+
418
+            $publicKeys = $this->keyManager->addSystemKeys($accessList, $publicKeys, $this->getOwner($path));
419
+
420
+            $encryptedFileKey = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys);
421
+
422
+            $this->keyManager->deleteAllFileKeys($path);
423
+
424
+            $this->keyManager->setAllFileKeys($path, $encryptedFileKey);
425
+
426
+        } else {
427
+            $this->logger->debug('no file key found, we assume that the file "{file}" is not encrypted',
428
+                ['file' => $path, 'app' => 'encryption']);
429
+
430
+            return false;
431
+        }
432
+
433
+        return true;
434
+    }
435
+
436
+    /**
437
+     * should the file be encrypted or not
438
+     *
439
+     * @param string $path
440
+     * @return boolean
441
+     */
442
+    public function shouldEncrypt($path) {
443
+        if ($this->util->shouldEncryptHomeStorage() === false) {
444
+            $storage = $this->util->getStorage($path);
445
+            if ($storage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
446
+                return false;
447
+            }
448
+        }
449
+        $parts = explode('/', $path);
450
+        if (count($parts) < 4) {
451
+            return false;
452
+        }
453
+
454
+        if ($parts[2] === 'files') {
455
+            return true;
456
+        }
457
+        if ($parts[2] === 'files_versions') {
458
+            return true;
459
+        }
460
+        if ($parts[2] === 'files_trashbin') {
461
+            return true;
462
+        }
463
+
464
+        return false;
465
+    }
466
+
467
+    /**
468
+     * get size of the unencrypted payload per block.
469
+     * Nextcloud read/write files with a block size of 8192 byte
470
+     *
471
+     * @param bool $signed
472
+     * @return int
473
+     */
474
+    public function getUnencryptedBlockSize($signed = false) {
475
+        if ($signed === false) {
476
+            return $this->unencryptedBlockSize;
477
+        }
478
+
479
+        return $this->unencryptedBlockSizeSigned;
480
+    }
481
+
482
+    /**
483
+     * check if the encryption module is able to read the file,
484
+     * e.g. if all encryption keys exists
485
+     *
486
+     * @param string $path
487
+     * @param string $uid user for whom we want to check if he can read the file
488
+     * @return bool
489
+     * @throws DecryptionFailedException
490
+     */
491
+    public function isReadable($path, $uid) {
492
+        $fileKey = $this->keyManager->getFileKey($path, $uid);
493
+        if (empty($fileKey)) {
494
+            $owner = $this->util->getOwner($path);
495
+            if ($owner !== $uid) {
496
+                // if it is a shared file we throw a exception with a useful
497
+                // error message because in this case it means that the file was
498
+                // shared with the user at a point where the user didn't had a
499
+                // valid private/public key
500
+                $msg = 'Encryption module "' . $this->getDisplayName() .
501
+                    '" is not able to read ' . $path;
502
+                $hint = $this->l->t('Can not read this file, probably this is a shared file. Please ask the file owner to reshare the file with you.');
503
+                $this->logger->warning($msg);
504
+                throw new DecryptionFailedException($msg, $hint);
505
+            }
506
+            return false;
507
+        }
508
+
509
+        return true;
510
+    }
511
+
512
+    /**
513
+     * Initial encryption of all files
514
+     *
515
+     * @param InputInterface $input
516
+     * @param OutputInterface $output write some status information to the terminal during encryption
517
+     */
518
+    public function encryptAll(InputInterface $input, OutputInterface $output) {
519
+        $this->encryptAll->encryptAll($input, $output);
520
+    }
521
+
522
+    /**
523
+     * prepare module to perform decrypt all operation
524
+     *
525
+     * @param InputInterface $input
526
+     * @param OutputInterface $output
527
+     * @param string $user
528
+     * @return bool
529
+     */
530
+    public function prepareDecryptAll(InputInterface $input, OutputInterface $output, $user = '') {
531
+        return $this->decryptAll->prepare($input, $output, $user);
532
+    }
533
+
534
+
535
+    /**
536
+     * @param string $path
537
+     * @return string
538
+     */
539
+    protected function getPathToRealFile($path) {
540
+        $realPath = $path;
541
+        $parts = explode('/', $path);
542
+        if ($parts[2] === 'files_versions') {
543
+            $realPath = '/' . $parts[1] . '/files/' . implode('/', array_slice($parts, 3));
544
+            $length = strrpos($realPath, '.');
545
+            $realPath = substr($realPath, 0, $length);
546
+        }
547
+
548
+        return $realPath;
549
+    }
550
+
551
+    /**
552
+     * remove .part file extension and the ocTransferId from the file to get the
553
+     * original file name
554
+     *
555
+     * @param string $path
556
+     * @return string
557
+     */
558
+    protected function stripPartFileExtension($path) {
559
+        if (pathinfo($path, PATHINFO_EXTENSION) === 'part') {
560
+            $pos = strrpos($path, '.', -6);
561
+            $path = substr($path, 0, $pos);
562
+        }
563
+
564
+        return $path;
565
+    }
566
+
567
+    /**
568
+     * get owner of a file
569
+     *
570
+     * @param string $path
571
+     * @return string
572
+     */
573
+    protected function getOwner($path) {
574
+        if (!isset($this->owner[$path])) {
575
+            $this->owner[$path] = $this->util->getOwner($path);
576
+        }
577
+        return $this->owner[$path];
578
+    }
579
+
580
+    /**
581
+     * Check if the module is ready to be used by that specific user.
582
+     * In case a module is not ready - because e.g. key pairs have not been generated
583
+     * upon login this method can return false before any operation starts and might
584
+     * cause issues during operations.
585
+     *
586
+     * @param string $user
587
+     * @return boolean
588
+     * @since 9.1.0
589
+     */
590
+    public function isReadyForUser($user) {
591
+        return $this->keyManager->userHasKeys($user);
592
+    }
593
+
594
+    /**
595
+     * We only need a detailed access list if the master key is not enabled
596
+     *
597
+     * @return bool
598
+     */
599
+    public function needDetailedAccessList() {
600
+        return !$this->util->isMasterKeyEnabled();
601
+    }
602 602
 }
Please login to merge, or discard this patch.
apps/encryption/lib/Crypto/EncryptAll.php 1 patch
Indentation   +435 added lines, -435 removed lines patch added patch discarded remove patch
@@ -46,440 +46,440 @@
 block discarded – undo
46 46
 
47 47
 class EncryptAll {
48 48
 
49
-	/** @var Setup */
50
-	protected $userSetup;
51
-
52
-	/** @var IUserManager */
53
-	protected $userManager;
54
-
55
-	/** @var View */
56
-	protected $rootView;
57
-
58
-	/** @var KeyManager */
59
-	protected $keyManager;
60
-
61
-	/** @var Util */
62
-	protected $util;
63
-
64
-	/** @var array  */
65
-	protected $userPasswords;
66
-
67
-	/** @var  IConfig */
68
-	protected $config;
69
-
70
-	/** @var IMailer */
71
-	protected $mailer;
72
-
73
-	/** @var  IL10N */
74
-	protected $l;
75
-
76
-	/** @var  QuestionHelper */
77
-	protected $questionHelper;
78
-
79
-	/** @var  OutputInterface */
80
-	protected $output;
81
-
82
-	/** @var  InputInterface */
83
-	protected $input;
84
-
85
-	/** @var ISecureRandom */
86
-	protected $secureRandom;
87
-
88
-	/**
89
-	 * @param Setup $userSetup
90
-	 * @param IUserManager $userManager
91
-	 * @param View $rootView
92
-	 * @param KeyManager $keyManager
93
-	 * @param Util $util
94
-	 * @param IConfig $config
95
-	 * @param IMailer $mailer
96
-	 * @param IL10N $l
97
-	 * @param QuestionHelper $questionHelper
98
-	 * @param ISecureRandom $secureRandom
99
-	 */
100
-	public function __construct(
101
-		Setup $userSetup,
102
-		IUserManager $userManager,
103
-		View $rootView,
104
-		KeyManager $keyManager,
105
-		Util $util,
106
-		IConfig $config,
107
-		IMailer $mailer,
108
-		IL10N $l,
109
-		QuestionHelper $questionHelper,
110
-		ISecureRandom $secureRandom
111
-	) {
112
-		$this->userSetup = $userSetup;
113
-		$this->userManager = $userManager;
114
-		$this->rootView = $rootView;
115
-		$this->keyManager = $keyManager;
116
-		$this->util = $util;
117
-		$this->config = $config;
118
-		$this->mailer = $mailer;
119
-		$this->l = $l;
120
-		$this->questionHelper = $questionHelper;
121
-		$this->secureRandom = $secureRandom;
122
-		// store one time passwords for the users
123
-		$this->userPasswords = [];
124
-	}
125
-
126
-	/**
127
-	 * start to encrypt all files
128
-	 *
129
-	 * @param InputInterface $input
130
-	 * @param OutputInterface $output
131
-	 */
132
-	public function encryptAll(InputInterface $input, OutputInterface $output) {
133
-
134
-		$this->input = $input;
135
-		$this->output = $output;
136
-
137
-		$headline = 'Encrypt all files with the ' . Encryption::DISPLAY_NAME;
138
-		$this->output->writeln("\n");
139
-		$this->output->writeln($headline);
140
-		$this->output->writeln(str_pad('', strlen($headline), '='));
141
-		$this->output->writeln("\n");
142
-
143
-		if ($this->util->isMasterKeyEnabled()) {
144
-			$this->output->writeln('Use master key to encrypt all files.');
145
-			$this->keyManager->validateMasterKey();
146
-		} else {
147
-			//create private/public keys for each user and store the private key password
148
-			$this->output->writeln('Create key-pair for every user');
149
-			$this->output->writeln('------------------------------');
150
-			$this->output->writeln('');
151
-			$this->output->writeln('This module will encrypt all files in the users files folder initially.');
152
-			$this->output->writeln('Already existing versions and files in the trash bin will not be encrypted.');
153
-			$this->output->writeln('');
154
-			$this->createKeyPairs();
155
-		}
156
-
157
-
158
-		// output generated encryption key passwords
159
-		if ($this->util->isMasterKeyEnabled() === false) {
160
-			//send-out or display password list and write it to a file
161
-			$this->output->writeln("\n");
162
-			$this->output->writeln('Generated encryption key passwords');
163
-			$this->output->writeln('----------------------------------');
164
-			$this->output->writeln('');
165
-			$this->outputPasswords();
166
-		}
167
-
168
-		//setup users file system and encrypt all files one by one (take should encrypt setting of storage into account)
169
-		$this->output->writeln("\n");
170
-		$this->output->writeln('Start to encrypt users files');
171
-		$this->output->writeln('----------------------------');
172
-		$this->output->writeln('');
173
-		$this->encryptAllUsersFiles();
174
-		$this->output->writeln("\n");
175
-	}
176
-
177
-	/**
178
-	 * create key-pair for every user
179
-	 */
180
-	protected function createKeyPairs() {
181
-		$this->output->writeln("\n");
182
-		$progress = new ProgressBar($this->output);
183
-		$progress->setFormat(" %message% \n [%bar%]");
184
-		$progress->start();
185
-
186
-		foreach($this->userManager->getBackends() as $backend) {
187
-			$limit = 500;
188
-			$offset = 0;
189
-			do {
190
-				$users = $backend->getUsers('', $limit, $offset);
191
-				foreach ($users as $user) {
192
-					if ($this->keyManager->userHasKeys($user) === false) {
193
-						$progress->setMessage('Create key-pair for ' . $user);
194
-						$progress->advance();
195
-						$this->setupUserFS($user);
196
-						$password = $this->generateOneTimePassword($user);
197
-						$this->userSetup->setupUser($user, $password);
198
-					} else {
199
-						// users which already have a key-pair will be stored with a
200
-						// empty password and filtered out later
201
-						$this->userPasswords[$user] = '';
202
-					}
203
-				}
204
-				$offset += $limit;
205
-			} while(count($users) >= $limit);
206
-		}
207
-
208
-		$progress->setMessage('Key-pair created for all users');
209
-		$progress->finish();
210
-	}
211
-
212
-	/**
213
-	 * iterate over all user and encrypt their files
214
-	 */
215
-	protected function encryptAllUsersFiles() {
216
-		$this->output->writeln("\n");
217
-		$progress = new ProgressBar($this->output);
218
-		$progress->setFormat(" %message% \n [%bar%]");
219
-		$progress->start();
220
-		$numberOfUsers = count($this->userPasswords);
221
-		$userNo = 1;
222
-		if ($this->util->isMasterKeyEnabled()) {
223
-			$this->encryptAllUserFilesWithMasterKey($progress);
224
-		} else {
225
-			foreach ($this->userPasswords as $uid => $password) {
226
-				$userCount = "$uid ($userNo of $numberOfUsers)";
227
-				$this->encryptUsersFiles($uid, $progress, $userCount);
228
-				$userNo++;
229
-			}
230
-		}
231
-		$progress->setMessage("all files encrypted");
232
-		$progress->finish();
233
-
234
-	}
235
-
236
-	/**
237
-	 * encrypt all user files with the master key
238
-	 *
239
-	 * @param ProgressBar $progress
240
-	 */
241
-	protected function encryptAllUserFilesWithMasterKey(ProgressBar $progress) {
242
-		$userNo = 1;
243
-		foreach($this->userManager->getBackends() as $backend) {
244
-			$limit = 500;
245
-			$offset = 0;
246
-			do {
247
-				$users = $backend->getUsers('', $limit, $offset);
248
-				foreach ($users as $user) {
249
-					$userCount = "$user ($userNo)";
250
-					$this->encryptUsersFiles($user, $progress, $userCount);
251
-					$userNo++;
252
-				}
253
-				$offset += $limit;
254
-			} while(count($users) >= $limit);
255
-		}
256
-	}
257
-
258
-	/**
259
-	 * encrypt files from the given user
260
-	 *
261
-	 * @param string $uid
262
-	 * @param ProgressBar $progress
263
-	 * @param string $userCount
264
-	 */
265
-	protected function encryptUsersFiles($uid, ProgressBar $progress, $userCount) {
266
-
267
-		$this->setupUserFS($uid);
268
-		$directories = [];
269
-		$directories[] =  '/' . $uid . '/files';
270
-
271
-		while($root = array_pop($directories)) {
272
-			$content = $this->rootView->getDirectoryContent($root);
273
-			foreach ($content as $file) {
274
-				$path = $root . '/' . $file['name'];
275
-				if ($this->rootView->is_dir($path)) {
276
-					$directories[] = $path;
277
-					continue;
278
-				} else {
279
-					$progress->setMessage("encrypt files for user $userCount: $path");
280
-					$progress->advance();
281
-					if($this->encryptFile($path) === false) {
282
-						$progress->setMessage("encrypt files for user $userCount: $path (already encrypted)");
283
-						$progress->advance();
284
-					}
285
-				}
286
-			}
287
-		}
288
-	}
289
-
290
-	/**
291
-	 * encrypt file
292
-	 *
293
-	 * @param string $path
294
-	 * @return bool
295
-	 */
296
-	protected function encryptFile($path) {
297
-
298
-		// skip already encrypted files
299
-		$fileInfo = $this->rootView->getFileInfo($path);
300
-		if ($fileInfo !== false && $fileInfo->isEncrypted()) {
301
-			return true;
302
-		}
303
-
304
-		$source = $path;
305
-		$target = $path . '.encrypted.' . time();
306
-
307
-		try {
308
-			$this->rootView->copy($source, $target);
309
-			$this->rootView->rename($target, $source);
310
-		} catch (DecryptionFailedException $e) {
311
-			if ($this->rootView->file_exists($target)) {
312
-				$this->rootView->unlink($target);
313
-			}
314
-			return false;
315
-		}
316
-
317
-		return true;
318
-	}
319
-
320
-	/**
321
-	 * output one-time encryption passwords
322
-	 */
323
-	protected function outputPasswords() {
324
-		$table = new Table($this->output);
325
-		$table->setHeaders(['Username', 'Private key password']);
326
-
327
-		//create rows
328
-		$newPasswords = [];
329
-		$unchangedPasswords = [];
330
-		foreach ($this->userPasswords as $uid => $password) {
331
-			if (empty($password)) {
332
-				$unchangedPasswords[] = $uid;
333
-			} else {
334
-				$newPasswords[] = [$uid, $password];
335
-			}
336
-		}
337
-
338
-		if (empty($newPasswords)) {
339
-			$this->output->writeln("\nAll users already had a key-pair, no further action needed.\n");
340
-			return;
341
-		}
342
-
343
-		$table->setRows($newPasswords);
344
-		$table->render();
345
-
346
-		if (!empty($unchangedPasswords)) {
347
-			$this->output->writeln("\nThe following users already had a key-pair which was reused without setting a new password:\n");
348
-			foreach ($unchangedPasswords as $uid) {
349
-				$this->output->writeln("    $uid");
350
-			}
351
-		}
352
-
353
-		$this->writePasswordsToFile($newPasswords);
354
-
355
-		$this->output->writeln('');
356
-		$question = new ConfirmationQuestion('Do you want to send the passwords directly to the users by mail? (y/n) ', false);
357
-		if ($this->questionHelper->ask($this->input, $this->output, $question)) {
358
-			$this->sendPasswordsByMail();
359
-		}
360
-	}
361
-
362
-	/**
363
-	 * write one-time encryption passwords to a csv file
364
-	 *
365
-	 * @param array $passwords
366
-	 */
367
-	protected function writePasswordsToFile(array $passwords) {
368
-		$fp = $this->rootView->fopen('oneTimeEncryptionPasswords.csv', 'w');
369
-		foreach ($passwords as $pwd) {
370
-			fputcsv($fp, $pwd);
371
-		}
372
-		fclose($fp);
373
-		$this->output->writeln("\n");
374
-		$this->output->writeln('A list of all newly created passwords was written to data/oneTimeEncryptionPasswords.csv');
375
-		$this->output->writeln('');
376
-		$this->output->writeln('Each of these users need to login to the web interface, go to the');
377
-		$this->output->writeln('personal settings section "basic encryption module" and');
378
-		$this->output->writeln('update the private key password to match the login password again by');
379
-		$this->output->writeln('entering the one-time password into the "old log-in password" field');
380
-		$this->output->writeln('and their current login password');
381
-	}
382
-
383
-	/**
384
-	 * setup user file system
385
-	 *
386
-	 * @param string $uid
387
-	 */
388
-	protected function setupUserFS($uid) {
389
-		\OC_Util::tearDownFS();
390
-		\OC_Util::setupFS($uid);
391
-	}
392
-
393
-	/**
394
-	 * generate one time password for the user and store it in a array
395
-	 *
396
-	 * @param string $uid
397
-	 * @return string password
398
-	 */
399
-	protected function generateOneTimePassword($uid) {
400
-		$password = $this->secureRandom->generate(8);
401
-		$this->userPasswords[$uid] = $password;
402
-		return $password;
403
-	}
404
-
405
-	/**
406
-	 * send encryption key passwords to the users by mail
407
-	 */
408
-	protected function sendPasswordsByMail() {
409
-		$noMail = [];
410
-
411
-		$this->output->writeln('');
412
-		$progress = new ProgressBar($this->output, count($this->userPasswords));
413
-		$progress->start();
414
-
415
-		foreach ($this->userPasswords as $uid => $password) {
416
-			$progress->advance();
417
-			if (!empty($password)) {
418
-				$recipient = $this->userManager->get($uid);
419
-				$recipientDisplayName = $recipient->getDisplayName();
420
-				$to = $recipient->getEMailAddress();
421
-
422
-				if ($to === '') {
423
-					$noMail[] = $uid;
424
-					continue;
425
-				}
426
-
427
-				$subject = (string)$this->l->t('one-time password for server-side-encryption');
428
-				list($htmlBody, $textBody) = $this->createMailBody($password);
429
-
430
-				// send it out now
431
-				try {
432
-					$message = $this->mailer->createMessage();
433
-					$message->setSubject($subject);
434
-					$message->setTo([$to => $recipientDisplayName]);
435
-					$message->setHtmlBody($htmlBody);
436
-					$message->setPlainBody($textBody);
437
-					$message->setFrom([
438
-						\OCP\Util::getDefaultEmailAddress('admin-noreply')
439
-					]);
440
-
441
-					$this->mailer->send($message);
442
-				} catch (\Exception $e) {
443
-					$noMail[] = $uid;
444
-				}
445
-			}
446
-		}
447
-
448
-		$progress->finish();
449
-
450
-		if (empty($noMail)) {
451
-			$this->output->writeln("\n\nPassword successfully send to all users");
452
-		} else {
453
-			$table = new Table($this->output);
454
-			$table->setHeaders(['Username', 'Private key password']);
455
-			$this->output->writeln("\n\nCould not send password to following users:\n");
456
-			$rows = [];
457
-			foreach ($noMail as $uid) {
458
-				$rows[] = [$uid, $this->userPasswords[$uid]];
459
-			}
460
-			$table->setRows($rows);
461
-			$table->render();
462
-		}
463
-
464
-	}
465
-
466
-	/**
467
-	 * create mail body for plain text and html mail
468
-	 *
469
-	 * @param string $password one-time encryption password
470
-	 * @return array an array of the html mail body and the plain text mail body
471
-	 */
472
-	protected function createMailBody($password) {
473
-
474
-		$html = new \OC_Template("encryption", "mail", "");
475
-		$html->assign ('password', $password);
476
-		$htmlMail = $html->fetchPage();
477
-
478
-		$plainText = new \OC_Template("encryption", "altmail", "");
479
-		$plainText->assign ('password', $password);
480
-		$plainTextMail = $plainText->fetchPage();
481
-
482
-		return [$htmlMail, $plainTextMail];
483
-	}
49
+    /** @var Setup */
50
+    protected $userSetup;
51
+
52
+    /** @var IUserManager */
53
+    protected $userManager;
54
+
55
+    /** @var View */
56
+    protected $rootView;
57
+
58
+    /** @var KeyManager */
59
+    protected $keyManager;
60
+
61
+    /** @var Util */
62
+    protected $util;
63
+
64
+    /** @var array  */
65
+    protected $userPasswords;
66
+
67
+    /** @var  IConfig */
68
+    protected $config;
69
+
70
+    /** @var IMailer */
71
+    protected $mailer;
72
+
73
+    /** @var  IL10N */
74
+    protected $l;
75
+
76
+    /** @var  QuestionHelper */
77
+    protected $questionHelper;
78
+
79
+    /** @var  OutputInterface */
80
+    protected $output;
81
+
82
+    /** @var  InputInterface */
83
+    protected $input;
84
+
85
+    /** @var ISecureRandom */
86
+    protected $secureRandom;
87
+
88
+    /**
89
+     * @param Setup $userSetup
90
+     * @param IUserManager $userManager
91
+     * @param View $rootView
92
+     * @param KeyManager $keyManager
93
+     * @param Util $util
94
+     * @param IConfig $config
95
+     * @param IMailer $mailer
96
+     * @param IL10N $l
97
+     * @param QuestionHelper $questionHelper
98
+     * @param ISecureRandom $secureRandom
99
+     */
100
+    public function __construct(
101
+        Setup $userSetup,
102
+        IUserManager $userManager,
103
+        View $rootView,
104
+        KeyManager $keyManager,
105
+        Util $util,
106
+        IConfig $config,
107
+        IMailer $mailer,
108
+        IL10N $l,
109
+        QuestionHelper $questionHelper,
110
+        ISecureRandom $secureRandom
111
+    ) {
112
+        $this->userSetup = $userSetup;
113
+        $this->userManager = $userManager;
114
+        $this->rootView = $rootView;
115
+        $this->keyManager = $keyManager;
116
+        $this->util = $util;
117
+        $this->config = $config;
118
+        $this->mailer = $mailer;
119
+        $this->l = $l;
120
+        $this->questionHelper = $questionHelper;
121
+        $this->secureRandom = $secureRandom;
122
+        // store one time passwords for the users
123
+        $this->userPasswords = [];
124
+    }
125
+
126
+    /**
127
+     * start to encrypt all files
128
+     *
129
+     * @param InputInterface $input
130
+     * @param OutputInterface $output
131
+     */
132
+    public function encryptAll(InputInterface $input, OutputInterface $output) {
133
+
134
+        $this->input = $input;
135
+        $this->output = $output;
136
+
137
+        $headline = 'Encrypt all files with the ' . Encryption::DISPLAY_NAME;
138
+        $this->output->writeln("\n");
139
+        $this->output->writeln($headline);
140
+        $this->output->writeln(str_pad('', strlen($headline), '='));
141
+        $this->output->writeln("\n");
142
+
143
+        if ($this->util->isMasterKeyEnabled()) {
144
+            $this->output->writeln('Use master key to encrypt all files.');
145
+            $this->keyManager->validateMasterKey();
146
+        } else {
147
+            //create private/public keys for each user and store the private key password
148
+            $this->output->writeln('Create key-pair for every user');
149
+            $this->output->writeln('------------------------------');
150
+            $this->output->writeln('');
151
+            $this->output->writeln('This module will encrypt all files in the users files folder initially.');
152
+            $this->output->writeln('Already existing versions and files in the trash bin will not be encrypted.');
153
+            $this->output->writeln('');
154
+            $this->createKeyPairs();
155
+        }
156
+
157
+
158
+        // output generated encryption key passwords
159
+        if ($this->util->isMasterKeyEnabled() === false) {
160
+            //send-out or display password list and write it to a file
161
+            $this->output->writeln("\n");
162
+            $this->output->writeln('Generated encryption key passwords');
163
+            $this->output->writeln('----------------------------------');
164
+            $this->output->writeln('');
165
+            $this->outputPasswords();
166
+        }
167
+
168
+        //setup users file system and encrypt all files one by one (take should encrypt setting of storage into account)
169
+        $this->output->writeln("\n");
170
+        $this->output->writeln('Start to encrypt users files');
171
+        $this->output->writeln('----------------------------');
172
+        $this->output->writeln('');
173
+        $this->encryptAllUsersFiles();
174
+        $this->output->writeln("\n");
175
+    }
176
+
177
+    /**
178
+     * create key-pair for every user
179
+     */
180
+    protected function createKeyPairs() {
181
+        $this->output->writeln("\n");
182
+        $progress = new ProgressBar($this->output);
183
+        $progress->setFormat(" %message% \n [%bar%]");
184
+        $progress->start();
185
+
186
+        foreach($this->userManager->getBackends() as $backend) {
187
+            $limit = 500;
188
+            $offset = 0;
189
+            do {
190
+                $users = $backend->getUsers('', $limit, $offset);
191
+                foreach ($users as $user) {
192
+                    if ($this->keyManager->userHasKeys($user) === false) {
193
+                        $progress->setMessage('Create key-pair for ' . $user);
194
+                        $progress->advance();
195
+                        $this->setupUserFS($user);
196
+                        $password = $this->generateOneTimePassword($user);
197
+                        $this->userSetup->setupUser($user, $password);
198
+                    } else {
199
+                        // users which already have a key-pair will be stored with a
200
+                        // empty password and filtered out later
201
+                        $this->userPasswords[$user] = '';
202
+                    }
203
+                }
204
+                $offset += $limit;
205
+            } while(count($users) >= $limit);
206
+        }
207
+
208
+        $progress->setMessage('Key-pair created for all users');
209
+        $progress->finish();
210
+    }
211
+
212
+    /**
213
+     * iterate over all user and encrypt their files
214
+     */
215
+    protected function encryptAllUsersFiles() {
216
+        $this->output->writeln("\n");
217
+        $progress = new ProgressBar($this->output);
218
+        $progress->setFormat(" %message% \n [%bar%]");
219
+        $progress->start();
220
+        $numberOfUsers = count($this->userPasswords);
221
+        $userNo = 1;
222
+        if ($this->util->isMasterKeyEnabled()) {
223
+            $this->encryptAllUserFilesWithMasterKey($progress);
224
+        } else {
225
+            foreach ($this->userPasswords as $uid => $password) {
226
+                $userCount = "$uid ($userNo of $numberOfUsers)";
227
+                $this->encryptUsersFiles($uid, $progress, $userCount);
228
+                $userNo++;
229
+            }
230
+        }
231
+        $progress->setMessage("all files encrypted");
232
+        $progress->finish();
233
+
234
+    }
235
+
236
+    /**
237
+     * encrypt all user files with the master key
238
+     *
239
+     * @param ProgressBar $progress
240
+     */
241
+    protected function encryptAllUserFilesWithMasterKey(ProgressBar $progress) {
242
+        $userNo = 1;
243
+        foreach($this->userManager->getBackends() as $backend) {
244
+            $limit = 500;
245
+            $offset = 0;
246
+            do {
247
+                $users = $backend->getUsers('', $limit, $offset);
248
+                foreach ($users as $user) {
249
+                    $userCount = "$user ($userNo)";
250
+                    $this->encryptUsersFiles($user, $progress, $userCount);
251
+                    $userNo++;
252
+                }
253
+                $offset += $limit;
254
+            } while(count($users) >= $limit);
255
+        }
256
+    }
257
+
258
+    /**
259
+     * encrypt files from the given user
260
+     *
261
+     * @param string $uid
262
+     * @param ProgressBar $progress
263
+     * @param string $userCount
264
+     */
265
+    protected function encryptUsersFiles($uid, ProgressBar $progress, $userCount) {
266
+
267
+        $this->setupUserFS($uid);
268
+        $directories = [];
269
+        $directories[] =  '/' . $uid . '/files';
270
+
271
+        while($root = array_pop($directories)) {
272
+            $content = $this->rootView->getDirectoryContent($root);
273
+            foreach ($content as $file) {
274
+                $path = $root . '/' . $file['name'];
275
+                if ($this->rootView->is_dir($path)) {
276
+                    $directories[] = $path;
277
+                    continue;
278
+                } else {
279
+                    $progress->setMessage("encrypt files for user $userCount: $path");
280
+                    $progress->advance();
281
+                    if($this->encryptFile($path) === false) {
282
+                        $progress->setMessage("encrypt files for user $userCount: $path (already encrypted)");
283
+                        $progress->advance();
284
+                    }
285
+                }
286
+            }
287
+        }
288
+    }
289
+
290
+    /**
291
+     * encrypt file
292
+     *
293
+     * @param string $path
294
+     * @return bool
295
+     */
296
+    protected function encryptFile($path) {
297
+
298
+        // skip already encrypted files
299
+        $fileInfo = $this->rootView->getFileInfo($path);
300
+        if ($fileInfo !== false && $fileInfo->isEncrypted()) {
301
+            return true;
302
+        }
303
+
304
+        $source = $path;
305
+        $target = $path . '.encrypted.' . time();
306
+
307
+        try {
308
+            $this->rootView->copy($source, $target);
309
+            $this->rootView->rename($target, $source);
310
+        } catch (DecryptionFailedException $e) {
311
+            if ($this->rootView->file_exists($target)) {
312
+                $this->rootView->unlink($target);
313
+            }
314
+            return false;
315
+        }
316
+
317
+        return true;
318
+    }
319
+
320
+    /**
321
+     * output one-time encryption passwords
322
+     */
323
+    protected function outputPasswords() {
324
+        $table = new Table($this->output);
325
+        $table->setHeaders(['Username', 'Private key password']);
326
+
327
+        //create rows
328
+        $newPasswords = [];
329
+        $unchangedPasswords = [];
330
+        foreach ($this->userPasswords as $uid => $password) {
331
+            if (empty($password)) {
332
+                $unchangedPasswords[] = $uid;
333
+            } else {
334
+                $newPasswords[] = [$uid, $password];
335
+            }
336
+        }
337
+
338
+        if (empty($newPasswords)) {
339
+            $this->output->writeln("\nAll users already had a key-pair, no further action needed.\n");
340
+            return;
341
+        }
342
+
343
+        $table->setRows($newPasswords);
344
+        $table->render();
345
+
346
+        if (!empty($unchangedPasswords)) {
347
+            $this->output->writeln("\nThe following users already had a key-pair which was reused without setting a new password:\n");
348
+            foreach ($unchangedPasswords as $uid) {
349
+                $this->output->writeln("    $uid");
350
+            }
351
+        }
352
+
353
+        $this->writePasswordsToFile($newPasswords);
354
+
355
+        $this->output->writeln('');
356
+        $question = new ConfirmationQuestion('Do you want to send the passwords directly to the users by mail? (y/n) ', false);
357
+        if ($this->questionHelper->ask($this->input, $this->output, $question)) {
358
+            $this->sendPasswordsByMail();
359
+        }
360
+    }
361
+
362
+    /**
363
+     * write one-time encryption passwords to a csv file
364
+     *
365
+     * @param array $passwords
366
+     */
367
+    protected function writePasswordsToFile(array $passwords) {
368
+        $fp = $this->rootView->fopen('oneTimeEncryptionPasswords.csv', 'w');
369
+        foreach ($passwords as $pwd) {
370
+            fputcsv($fp, $pwd);
371
+        }
372
+        fclose($fp);
373
+        $this->output->writeln("\n");
374
+        $this->output->writeln('A list of all newly created passwords was written to data/oneTimeEncryptionPasswords.csv');
375
+        $this->output->writeln('');
376
+        $this->output->writeln('Each of these users need to login to the web interface, go to the');
377
+        $this->output->writeln('personal settings section "basic encryption module" and');
378
+        $this->output->writeln('update the private key password to match the login password again by');
379
+        $this->output->writeln('entering the one-time password into the "old log-in password" field');
380
+        $this->output->writeln('and their current login password');
381
+    }
382
+
383
+    /**
384
+     * setup user file system
385
+     *
386
+     * @param string $uid
387
+     */
388
+    protected function setupUserFS($uid) {
389
+        \OC_Util::tearDownFS();
390
+        \OC_Util::setupFS($uid);
391
+    }
392
+
393
+    /**
394
+     * generate one time password for the user and store it in a array
395
+     *
396
+     * @param string $uid
397
+     * @return string password
398
+     */
399
+    protected function generateOneTimePassword($uid) {
400
+        $password = $this->secureRandom->generate(8);
401
+        $this->userPasswords[$uid] = $password;
402
+        return $password;
403
+    }
404
+
405
+    /**
406
+     * send encryption key passwords to the users by mail
407
+     */
408
+    protected function sendPasswordsByMail() {
409
+        $noMail = [];
410
+
411
+        $this->output->writeln('');
412
+        $progress = new ProgressBar($this->output, count($this->userPasswords));
413
+        $progress->start();
414
+
415
+        foreach ($this->userPasswords as $uid => $password) {
416
+            $progress->advance();
417
+            if (!empty($password)) {
418
+                $recipient = $this->userManager->get($uid);
419
+                $recipientDisplayName = $recipient->getDisplayName();
420
+                $to = $recipient->getEMailAddress();
421
+
422
+                if ($to === '') {
423
+                    $noMail[] = $uid;
424
+                    continue;
425
+                }
426
+
427
+                $subject = (string)$this->l->t('one-time password for server-side-encryption');
428
+                list($htmlBody, $textBody) = $this->createMailBody($password);
429
+
430
+                // send it out now
431
+                try {
432
+                    $message = $this->mailer->createMessage();
433
+                    $message->setSubject($subject);
434
+                    $message->setTo([$to => $recipientDisplayName]);
435
+                    $message->setHtmlBody($htmlBody);
436
+                    $message->setPlainBody($textBody);
437
+                    $message->setFrom([
438
+                        \OCP\Util::getDefaultEmailAddress('admin-noreply')
439
+                    ]);
440
+
441
+                    $this->mailer->send($message);
442
+                } catch (\Exception $e) {
443
+                    $noMail[] = $uid;
444
+                }
445
+            }
446
+        }
447
+
448
+        $progress->finish();
449
+
450
+        if (empty($noMail)) {
451
+            $this->output->writeln("\n\nPassword successfully send to all users");
452
+        } else {
453
+            $table = new Table($this->output);
454
+            $table->setHeaders(['Username', 'Private key password']);
455
+            $this->output->writeln("\n\nCould not send password to following users:\n");
456
+            $rows = [];
457
+            foreach ($noMail as $uid) {
458
+                $rows[] = [$uid, $this->userPasswords[$uid]];
459
+            }
460
+            $table->setRows($rows);
461
+            $table->render();
462
+        }
463
+
464
+    }
465
+
466
+    /**
467
+     * create mail body for plain text and html mail
468
+     *
469
+     * @param string $password one-time encryption password
470
+     * @return array an array of the html mail body and the plain text mail body
471
+     */
472
+    protected function createMailBody($password) {
473
+
474
+        $html = new \OC_Template("encryption", "mail", "");
475
+        $html->assign ('password', $password);
476
+        $htmlMail = $html->fetchPage();
477
+
478
+        $plainText = new \OC_Template("encryption", "altmail", "");
479
+        $plainText->assign ('password', $password);
480
+        $plainTextMail = $plainText->fetchPage();
481
+
482
+        return [$htmlMail, $plainTextMail];
483
+    }
484 484
 
485 485
 }
Please login to merge, or discard this patch.
apps/encryption/lib/AppInfo/Application.php 1 patch
Indentation   +213 added lines, -213 removed lines patch added patch discarded remove patch
@@ -50,217 +50,217 @@
 block discarded – undo
50 50
 
51 51
 class Application extends \OCP\AppFramework\App {
52 52
 
53
-	/** @var IManager */
54
-	private $encryptionManager;
55
-	/** @var IConfig */
56
-	private $config;
57
-
58
-	/**
59
-	 * @param array $urlParams
60
-	 */
61
-	public function __construct($urlParams = []) {
62
-		parent::__construct('encryption', $urlParams);
63
-		$this->encryptionManager = \OC::$server->getEncryptionManager();
64
-		$this->config = \OC::$server->getConfig();
65
-		$this->registerServices();
66
-	}
67
-
68
-	public function setUp() {
69
-		if ($this->encryptionManager->isEnabled()) {
70
-			/** @var Setup $setup */
71
-			$setup = $this->getContainer()->query('UserSetup');
72
-			$setup->setupSystem();
73
-		}
74
-	}
75
-
76
-	/**
77
-	 * register hooks
78
-	 */
79
-	public function registerHooks() {
80
-		if (!$this->config->getSystemValueBool('maintenance')) {
81
-
82
-			$container = $this->getContainer();
83
-			$server = $container->getServer();
84
-			// Register our hooks and fire them.
85
-			$hookManager = new HookManager();
86
-
87
-			$hookManager->registerHook([
88
-				new UserHooks($container->query('KeyManager'),
89
-					$server->getUserManager(),
90
-					$server->getLogger(),
91
-					$container->query('UserSetup'),
92
-					$server->getUserSession(),
93
-					$container->query('Util'),
94
-					$container->query('Session'),
95
-					$container->query('Crypt'),
96
-					$container->query('Recovery'))
97
-			]);
98
-
99
-			$hookManager->fireHooks();
100
-
101
-		} else {
102
-			// Logout user if we are in maintenance to force re-login
103
-			$this->getContainer()->getServer()->getUserSession()->logout();
104
-		}
105
-	}
106
-
107
-	public function registerEncryptionModule() {
108
-		$container = $this->getContainer();
109
-
110
-
111
-		$this->encryptionManager->registerEncryptionModule(
112
-			Encryption::ID,
113
-			Encryption::DISPLAY_NAME,
114
-			function() use ($container) {
115
-
116
-			return new Encryption(
117
-				$container->query('Crypt'),
118
-				$container->query('KeyManager'),
119
-				$container->query('Util'),
120
-				$container->query('Session'),
121
-				$container->query('EncryptAll'),
122
-				$container->query('DecryptAll'),
123
-				$container->getServer()->getLogger(),
124
-				$container->getServer()->getL10N($container->getAppName())
125
-			);
126
-		});
127
-
128
-	}
129
-
130
-	public function registerServices() {
131
-		$container = $this->getContainer();
132
-
133
-		$container->registerService('Crypt',
134
-			function (IAppContainer $c) {
135
-				$server = $c->getServer();
136
-				return new Crypt($server->getLogger(),
137
-					$server->getUserSession(),
138
-					$server->getConfig(),
139
-					$server->getL10N($c->getAppName()));
140
-			});
141
-
142
-		$container->registerService('Session',
143
-			function (IAppContainer $c) {
144
-				$server = $c->getServer();
145
-				return new Session($server->getSession());
146
-			}
147
-		);
148
-
149
-		$container->registerService('KeyManager',
150
-			function (IAppContainer $c) {
151
-				$server = $c->getServer();
152
-
153
-				return new KeyManager($server->getEncryptionKeyStorage(),
154
-					$c->query('Crypt'),
155
-					$server->getConfig(),
156
-					$server->getUserSession(),
157
-					new Session($server->getSession()),
158
-					$server->getLogger(),
159
-					$c->query('Util')
160
-				);
161
-			});
162
-
163
-		$container->registerService('Recovery',
164
-			function (IAppContainer $c) {
165
-				$server = $c->getServer();
166
-
167
-				return new Recovery(
168
-					$server->getUserSession(),
169
-					$c->query('Crypt'),
170
-					$c->query('KeyManager'),
171
-					$server->getConfig(),
172
-					$server->getEncryptionFilesHelper(),
173
-					new View());
174
-			});
175
-
176
-		$container->registerService('RecoveryController', function (IAppContainer $c) {
177
-			$server = $c->getServer();
178
-			return new RecoveryController(
179
-				$c->getAppName(),
180
-				$server->getRequest(),
181
-				$server->getConfig(),
182
-				$server->getL10N($c->getAppName()),
183
-				$c->query('Recovery'));
184
-		});
185
-
186
-		$container->registerService('StatusController', function (IAppContainer $c) {
187
-			$server = $c->getServer();
188
-			return new StatusController(
189
-				$c->getAppName(),
190
-				$server->getRequest(),
191
-				$server->getL10N($c->getAppName()),
192
-				$c->query('Session'),
193
-				$server->getEncryptionManager()
194
-			);
195
-		});
196
-
197
-		$container->registerService('SettingsController', function (IAppContainer $c) {
198
-			$server = $c->getServer();
199
-			return new SettingsController(
200
-				$c->getAppName(),
201
-				$server->getRequest(),
202
-				$server->getL10N($c->getAppName()),
203
-				$server->getUserManager(),
204
-				$server->getUserSession(),
205
-				$c->query('KeyManager'),
206
-				$c->query('Crypt'),
207
-				$c->query('Session'),
208
-				$server->getSession(),
209
-				$c->query('Util')
210
-			);
211
-		});
212
-
213
-		$container->registerService('UserSetup',
214
-			function (IAppContainer $c) {
215
-				$server = $c->getServer();
216
-				return new Setup($server->getLogger(),
217
-					$server->getUserSession(),
218
-					$c->query('Crypt'),
219
-					$c->query('KeyManager'));
220
-			});
221
-
222
-		$container->registerService('Util',
223
-			function (IAppContainer $c) {
224
-				$server = $c->getServer();
225
-
226
-				return new Util(
227
-					new View(),
228
-					$c->query('Crypt'),
229
-					$server->getLogger(),
230
-					$server->getUserSession(),
231
-					$server->getConfig(),
232
-					$server->getUserManager());
233
-			});
234
-
235
-		$container->registerService('EncryptAll',
236
-			function (IAppContainer $c) {
237
-				$server = $c->getServer();
238
-				return new EncryptAll(
239
-					$c->query('UserSetup'),
240
-					$c->getServer()->getUserManager(),
241
-					new View(),
242
-					$c->query('KeyManager'),
243
-					$c->query('Util'),
244
-					$server->getConfig(),
245
-					$server->getMailer(),
246
-					$server->getL10N('encryption'),
247
-					new QuestionHelper(),
248
-					$server->getSecureRandom()
249
-				);
250
-			}
251
-		);
252
-
253
-		$container->registerService('DecryptAll',
254
-			function (IAppContainer $c) {
255
-				return new DecryptAll(
256
-					$c->query('Util'),
257
-					$c->query('KeyManager'),
258
-					$c->query('Crypt'),
259
-					$c->query('Session'),
260
-					new QuestionHelper()
261
-				);
262
-			}
263
-		);
264
-
265
-	}
53
+    /** @var IManager */
54
+    private $encryptionManager;
55
+    /** @var IConfig */
56
+    private $config;
57
+
58
+    /**
59
+     * @param array $urlParams
60
+     */
61
+    public function __construct($urlParams = []) {
62
+        parent::__construct('encryption', $urlParams);
63
+        $this->encryptionManager = \OC::$server->getEncryptionManager();
64
+        $this->config = \OC::$server->getConfig();
65
+        $this->registerServices();
66
+    }
67
+
68
+    public function setUp() {
69
+        if ($this->encryptionManager->isEnabled()) {
70
+            /** @var Setup $setup */
71
+            $setup = $this->getContainer()->query('UserSetup');
72
+            $setup->setupSystem();
73
+        }
74
+    }
75
+
76
+    /**
77
+     * register hooks
78
+     */
79
+    public function registerHooks() {
80
+        if (!$this->config->getSystemValueBool('maintenance')) {
81
+
82
+            $container = $this->getContainer();
83
+            $server = $container->getServer();
84
+            // Register our hooks and fire them.
85
+            $hookManager = new HookManager();
86
+
87
+            $hookManager->registerHook([
88
+                new UserHooks($container->query('KeyManager'),
89
+                    $server->getUserManager(),
90
+                    $server->getLogger(),
91
+                    $container->query('UserSetup'),
92
+                    $server->getUserSession(),
93
+                    $container->query('Util'),
94
+                    $container->query('Session'),
95
+                    $container->query('Crypt'),
96
+                    $container->query('Recovery'))
97
+            ]);
98
+
99
+            $hookManager->fireHooks();
100
+
101
+        } else {
102
+            // Logout user if we are in maintenance to force re-login
103
+            $this->getContainer()->getServer()->getUserSession()->logout();
104
+        }
105
+    }
106
+
107
+    public function registerEncryptionModule() {
108
+        $container = $this->getContainer();
109
+
110
+
111
+        $this->encryptionManager->registerEncryptionModule(
112
+            Encryption::ID,
113
+            Encryption::DISPLAY_NAME,
114
+            function() use ($container) {
115
+
116
+            return new Encryption(
117
+                $container->query('Crypt'),
118
+                $container->query('KeyManager'),
119
+                $container->query('Util'),
120
+                $container->query('Session'),
121
+                $container->query('EncryptAll'),
122
+                $container->query('DecryptAll'),
123
+                $container->getServer()->getLogger(),
124
+                $container->getServer()->getL10N($container->getAppName())
125
+            );
126
+        });
127
+
128
+    }
129
+
130
+    public function registerServices() {
131
+        $container = $this->getContainer();
132
+
133
+        $container->registerService('Crypt',
134
+            function (IAppContainer $c) {
135
+                $server = $c->getServer();
136
+                return new Crypt($server->getLogger(),
137
+                    $server->getUserSession(),
138
+                    $server->getConfig(),
139
+                    $server->getL10N($c->getAppName()));
140
+            });
141
+
142
+        $container->registerService('Session',
143
+            function (IAppContainer $c) {
144
+                $server = $c->getServer();
145
+                return new Session($server->getSession());
146
+            }
147
+        );
148
+
149
+        $container->registerService('KeyManager',
150
+            function (IAppContainer $c) {
151
+                $server = $c->getServer();
152
+
153
+                return new KeyManager($server->getEncryptionKeyStorage(),
154
+                    $c->query('Crypt'),
155
+                    $server->getConfig(),
156
+                    $server->getUserSession(),
157
+                    new Session($server->getSession()),
158
+                    $server->getLogger(),
159
+                    $c->query('Util')
160
+                );
161
+            });
162
+
163
+        $container->registerService('Recovery',
164
+            function (IAppContainer $c) {
165
+                $server = $c->getServer();
166
+
167
+                return new Recovery(
168
+                    $server->getUserSession(),
169
+                    $c->query('Crypt'),
170
+                    $c->query('KeyManager'),
171
+                    $server->getConfig(),
172
+                    $server->getEncryptionFilesHelper(),
173
+                    new View());
174
+            });
175
+
176
+        $container->registerService('RecoveryController', function (IAppContainer $c) {
177
+            $server = $c->getServer();
178
+            return new RecoveryController(
179
+                $c->getAppName(),
180
+                $server->getRequest(),
181
+                $server->getConfig(),
182
+                $server->getL10N($c->getAppName()),
183
+                $c->query('Recovery'));
184
+        });
185
+
186
+        $container->registerService('StatusController', function (IAppContainer $c) {
187
+            $server = $c->getServer();
188
+            return new StatusController(
189
+                $c->getAppName(),
190
+                $server->getRequest(),
191
+                $server->getL10N($c->getAppName()),
192
+                $c->query('Session'),
193
+                $server->getEncryptionManager()
194
+            );
195
+        });
196
+
197
+        $container->registerService('SettingsController', function (IAppContainer $c) {
198
+            $server = $c->getServer();
199
+            return new SettingsController(
200
+                $c->getAppName(),
201
+                $server->getRequest(),
202
+                $server->getL10N($c->getAppName()),
203
+                $server->getUserManager(),
204
+                $server->getUserSession(),
205
+                $c->query('KeyManager'),
206
+                $c->query('Crypt'),
207
+                $c->query('Session'),
208
+                $server->getSession(),
209
+                $c->query('Util')
210
+            );
211
+        });
212
+
213
+        $container->registerService('UserSetup',
214
+            function (IAppContainer $c) {
215
+                $server = $c->getServer();
216
+                return new Setup($server->getLogger(),
217
+                    $server->getUserSession(),
218
+                    $c->query('Crypt'),
219
+                    $c->query('KeyManager'));
220
+            });
221
+
222
+        $container->registerService('Util',
223
+            function (IAppContainer $c) {
224
+                $server = $c->getServer();
225
+
226
+                return new Util(
227
+                    new View(),
228
+                    $c->query('Crypt'),
229
+                    $server->getLogger(),
230
+                    $server->getUserSession(),
231
+                    $server->getConfig(),
232
+                    $server->getUserManager());
233
+            });
234
+
235
+        $container->registerService('EncryptAll',
236
+            function (IAppContainer $c) {
237
+                $server = $c->getServer();
238
+                return new EncryptAll(
239
+                    $c->query('UserSetup'),
240
+                    $c->getServer()->getUserManager(),
241
+                    new View(),
242
+                    $c->query('KeyManager'),
243
+                    $c->query('Util'),
244
+                    $server->getConfig(),
245
+                    $server->getMailer(),
246
+                    $server->getL10N('encryption'),
247
+                    new QuestionHelper(),
248
+                    $server->getSecureRandom()
249
+                );
250
+            }
251
+        );
252
+
253
+        $container->registerService('DecryptAll',
254
+            function (IAppContainer $c) {
255
+                return new DecryptAll(
256
+                    $c->query('Util'),
257
+                    $c->query('KeyManager'),
258
+                    $c->query('Crypt'),
259
+                    $c->query('Session'),
260
+                    new QuestionHelper()
261
+                );
262
+            }
263
+        );
264
+
265
+    }
266 266
 }
Please login to merge, or discard this patch.
apps/encryption/lib/Recovery.php 1 patch
Indentation   +273 added lines, -273 removed lines patch added patch discarded remove patch
@@ -38,279 +38,279 @@
 block discarded – undo
38 38
 class Recovery {
39 39
 
40 40
 
41
-	/**
42
-	 * @var null|IUser
43
-	 */
44
-	protected $user;
45
-	/**
46
-	 * @var Crypt
47
-	 */
48
-	protected $crypt;
49
-	/**
50
-	 * @var KeyManager
51
-	 */
52
-	private $keyManager;
53
-	/**
54
-	 * @var IConfig
55
-	 */
56
-	private $config;
57
-	/**
58
-	 * @var View
59
-	 */
60
-	private $view;
61
-	/**
62
-	 * @var IFile
63
-	 */
64
-	private $file;
65
-
66
-	/**
67
-	 * @param IUserSession $userSession
68
-	 * @param Crypt $crypt
69
-	 * @param KeyManager $keyManager
70
-	 * @param IConfig $config
71
-	 * @param IFile $file
72
-	 * @param View $view
73
-	 */
74
-	public function __construct(IUserSession $userSession,
75
-								Crypt $crypt,
76
-								KeyManager $keyManager,
77
-								IConfig $config,
78
-								IFile $file,
79
-								View $view) {
80
-		$this->user = ($userSession && $userSession->isLoggedIn()) ? $userSession->getUser() : false;
81
-		$this->crypt = $crypt;
82
-		$this->keyManager = $keyManager;
83
-		$this->config = $config;
84
-		$this->view = $view;
85
-		$this->file = $file;
86
-	}
87
-
88
-	/**
89
-	 * @param string $password
90
-	 * @return bool
91
-	 */
92
-	public function enableAdminRecovery($password) {
93
-		$appConfig = $this->config;
94
-		$keyManager = $this->keyManager;
95
-
96
-		if (!$keyManager->recoveryKeyExists()) {
97
-			$keyPair = $this->crypt->createKeyPair();
98
-			if(!is_array($keyPair)) {
99
-				return false;
100
-			}
101
-
102
-			$this->keyManager->setRecoveryKey($password, $keyPair);
103
-		}
104
-
105
-		if ($keyManager->checkRecoveryPassword($password)) {
106
-			$appConfig->setAppValue('encryption', 'recoveryAdminEnabled', 1);
107
-			return true;
108
-		}
109
-
110
-		return false;
111
-	}
112
-
113
-	/**
114
-	 * change recovery key id
115
-	 *
116
-	 * @param string $newPassword
117
-	 * @param string $oldPassword
118
-	 * @return bool
119
-	 */
120
-	public function changeRecoveryKeyPassword($newPassword, $oldPassword) {
121
-		$recoveryKey = $this->keyManager->getSystemPrivateKey($this->keyManager->getRecoveryKeyId());
122
-		$decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $oldPassword);
123
-		if($decryptedRecoveryKey === false) {
124
-			return false;
125
-		}
126
-		$encryptedRecoveryKey = $this->crypt->encryptPrivateKey($decryptedRecoveryKey, $newPassword);
127
-		$header = $this->crypt->generateHeader();
128
-		if ($encryptedRecoveryKey) {
129
-			$this->keyManager->setSystemPrivateKey($this->keyManager->getRecoveryKeyId(), $header . $encryptedRecoveryKey);
130
-			return true;
131
-		}
132
-		return false;
133
-	}
134
-
135
-	/**
136
-	 * @param string $recoveryPassword
137
-	 * @return bool
138
-	 */
139
-	public function disableAdminRecovery($recoveryPassword) {
140
-		$keyManager = $this->keyManager;
141
-
142
-		if ($keyManager->checkRecoveryPassword($recoveryPassword)) {
143
-			// Set recoveryAdmin as disabled
144
-			$this->config->setAppValue('encryption', 'recoveryAdminEnabled', 0);
145
-			return true;
146
-		}
147
-		return false;
148
-	}
149
-
150
-	/**
151
-	 * check if recovery is enabled for user
152
-	 *
153
-	 * @param string $user if no user is given we check the current logged-in user
154
-	 *
155
-	 * @return bool
156
-	 */
157
-	public function isRecoveryEnabledForUser($user = '') {
158
-		$uid = $user === '' ? $this->user->getUID() : $user;
159
-		$recoveryMode = $this->config->getUserValue($uid,
160
-			'encryption',
161
-			'recoveryEnabled',
162
-			0);
163
-
164
-		return ($recoveryMode === '1');
165
-	}
166
-
167
-	/**
168
-	 * check if recovery is key is enabled by the administrator
169
-	 *
170
-	 * @return bool
171
-	 */
172
-	public function isRecoveryKeyEnabled() {
173
-		$enabled = $this->config->getAppValue('encryption', 'recoveryAdminEnabled', 0);
174
-
175
-		return ($enabled === '1');
176
-	}
177
-
178
-	/**
179
-	 * @param string $value
180
-	 * @return bool
181
-	 */
182
-	public function setRecoveryForUser($value) {
183
-
184
-		try {
185
-			$this->config->setUserValue($this->user->getUID(),
186
-				'encryption',
187
-				'recoveryEnabled',
188
-				$value);
189
-
190
-			if ($value === '1') {
191
-				$this->addRecoveryKeys('/' . $this->user->getUID() . '/files/');
192
-			} else {
193
-				$this->removeRecoveryKeys('/' . $this->user->getUID() . '/files/');
194
-			}
195
-
196
-			return true;
197
-		} catch (PreConditionNotMetException $e) {
198
-			return false;
199
-		}
200
-	}
201
-
202
-	/**
203
-	 * add recovery key to all encrypted files
204
-	 * @param string $path
205
-	 */
206
-	private function addRecoveryKeys($path) {
207
-		$dirContent = $this->view->getDirectoryContent($path);
208
-		foreach ($dirContent as $item) {
209
-			$filePath = $item->getPath();
210
-			if ($item['type'] === 'dir') {
211
-				$this->addRecoveryKeys($filePath . '/');
212
-			} else {
213
-				$fileKey = $this->keyManager->getFileKey($filePath, $this->user->getUID());
214
-				if (!empty($fileKey)) {
215
-					$accessList = $this->file->getAccessList($filePath);
216
-					$publicKeys = [];
217
-					foreach ($accessList['users'] as $uid) {
218
-						$publicKeys[$uid] = $this->keyManager->getPublicKey($uid);
219
-					}
220
-
221
-					$publicKeys = $this->keyManager->addSystemKeys($accessList, $publicKeys, $this->user->getUID());
222
-
223
-					$encryptedKeyfiles = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys);
224
-					$this->keyManager->setAllFileKeys($filePath, $encryptedKeyfiles);
225
-				}
226
-			}
227
-		}
228
-	}
229
-
230
-	/**
231
-	 * remove recovery key to all encrypted files
232
-	 * @param string $path
233
-	 */
234
-	private function removeRecoveryKeys($path) {
235
-		$dirContent = $this->view->getDirectoryContent($path);
236
-		foreach ($dirContent as $item) {
237
-			$filePath = $item->getPath();
238
-			if ($item['type'] === 'dir') {
239
-				$this->removeRecoveryKeys($filePath . '/');
240
-			} else {
241
-				$this->keyManager->deleteShareKey($filePath, $this->keyManager->getRecoveryKeyId());
242
-			}
243
-		}
244
-	}
245
-
246
-	/**
247
-	 * recover users files with the recovery key
248
-	 *
249
-	 * @param string $recoveryPassword
250
-	 * @param string $user
251
-	 */
252
-	public function recoverUsersFiles($recoveryPassword, $user) {
253
-		$encryptedKey = $this->keyManager->getSystemPrivateKey($this->keyManager->getRecoveryKeyId());
254
-
255
-		$privateKey = $this->crypt->decryptPrivateKey($encryptedKey, $recoveryPassword);
256
-		if($privateKey !== false) {
257
-			$this->recoverAllFiles('/' . $user . '/files/', $privateKey, $user);
258
-		}
259
-	}
260
-
261
-	/**
262
-	 * recover users files
263
-	 *
264
-	 * @param string $path
265
-	 * @param string $privateKey
266
-	 * @param string $uid
267
-	 */
268
-	private function recoverAllFiles($path, $privateKey, $uid) {
269
-		$dirContent = $this->view->getDirectoryContent($path);
270
-
271
-		foreach ($dirContent as $item) {
272
-			// Get relative path from encryption/keyfiles
273
-			$filePath = $item->getPath();
274
-			if ($this->view->is_dir($filePath)) {
275
-				$this->recoverAllFiles($filePath . '/', $privateKey, $uid);
276
-			} else {
277
-				$this->recoverFile($filePath, $privateKey, $uid);
278
-			}
279
-		}
280
-
281
-	}
282
-
283
-	/**
284
-	 * recover file
285
-	 *
286
-	 * @param string $path
287
-	 * @param string $privateKey
288
-	 * @param string $uid
289
-	 */
290
-	private function recoverFile($path, $privateKey, $uid) {
291
-		$encryptedFileKey = $this->keyManager->getEncryptedFileKey($path);
292
-		$shareKey = $this->keyManager->getShareKey($path, $this->keyManager->getRecoveryKeyId());
293
-
294
-		if ($encryptedFileKey && $shareKey && $privateKey) {
295
-			$fileKey = $this->crypt->multiKeyDecrypt($encryptedFileKey,
296
-				$shareKey,
297
-				$privateKey);
298
-		}
299
-
300
-		if (!empty($fileKey)) {
301
-			$accessList = $this->file->getAccessList($path);
302
-			$publicKeys = [];
303
-			foreach ($accessList['users'] as $user) {
304
-				$publicKeys[$user] = $this->keyManager->getPublicKey($user);
305
-			}
306
-
307
-			$publicKeys = $this->keyManager->addSystemKeys($accessList, $publicKeys, $uid);
308
-
309
-			$encryptedKeyfiles = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys);
310
-			$this->keyManager->setAllFileKeys($path, $encryptedKeyfiles);
311
-		}
312
-
313
-	}
41
+    /**
42
+     * @var null|IUser
43
+     */
44
+    protected $user;
45
+    /**
46
+     * @var Crypt
47
+     */
48
+    protected $crypt;
49
+    /**
50
+     * @var KeyManager
51
+     */
52
+    private $keyManager;
53
+    /**
54
+     * @var IConfig
55
+     */
56
+    private $config;
57
+    /**
58
+     * @var View
59
+     */
60
+    private $view;
61
+    /**
62
+     * @var IFile
63
+     */
64
+    private $file;
65
+
66
+    /**
67
+     * @param IUserSession $userSession
68
+     * @param Crypt $crypt
69
+     * @param KeyManager $keyManager
70
+     * @param IConfig $config
71
+     * @param IFile $file
72
+     * @param View $view
73
+     */
74
+    public function __construct(IUserSession $userSession,
75
+                                Crypt $crypt,
76
+                                KeyManager $keyManager,
77
+                                IConfig $config,
78
+                                IFile $file,
79
+                                View $view) {
80
+        $this->user = ($userSession && $userSession->isLoggedIn()) ? $userSession->getUser() : false;
81
+        $this->crypt = $crypt;
82
+        $this->keyManager = $keyManager;
83
+        $this->config = $config;
84
+        $this->view = $view;
85
+        $this->file = $file;
86
+    }
87
+
88
+    /**
89
+     * @param string $password
90
+     * @return bool
91
+     */
92
+    public function enableAdminRecovery($password) {
93
+        $appConfig = $this->config;
94
+        $keyManager = $this->keyManager;
95
+
96
+        if (!$keyManager->recoveryKeyExists()) {
97
+            $keyPair = $this->crypt->createKeyPair();
98
+            if(!is_array($keyPair)) {
99
+                return false;
100
+            }
101
+
102
+            $this->keyManager->setRecoveryKey($password, $keyPair);
103
+        }
104
+
105
+        if ($keyManager->checkRecoveryPassword($password)) {
106
+            $appConfig->setAppValue('encryption', 'recoveryAdminEnabled', 1);
107
+            return true;
108
+        }
109
+
110
+        return false;
111
+    }
112
+
113
+    /**
114
+     * change recovery key id
115
+     *
116
+     * @param string $newPassword
117
+     * @param string $oldPassword
118
+     * @return bool
119
+     */
120
+    public function changeRecoveryKeyPassword($newPassword, $oldPassword) {
121
+        $recoveryKey = $this->keyManager->getSystemPrivateKey($this->keyManager->getRecoveryKeyId());
122
+        $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $oldPassword);
123
+        if($decryptedRecoveryKey === false) {
124
+            return false;
125
+        }
126
+        $encryptedRecoveryKey = $this->crypt->encryptPrivateKey($decryptedRecoveryKey, $newPassword);
127
+        $header = $this->crypt->generateHeader();
128
+        if ($encryptedRecoveryKey) {
129
+            $this->keyManager->setSystemPrivateKey($this->keyManager->getRecoveryKeyId(), $header . $encryptedRecoveryKey);
130
+            return true;
131
+        }
132
+        return false;
133
+    }
134
+
135
+    /**
136
+     * @param string $recoveryPassword
137
+     * @return bool
138
+     */
139
+    public function disableAdminRecovery($recoveryPassword) {
140
+        $keyManager = $this->keyManager;
141
+
142
+        if ($keyManager->checkRecoveryPassword($recoveryPassword)) {
143
+            // Set recoveryAdmin as disabled
144
+            $this->config->setAppValue('encryption', 'recoveryAdminEnabled', 0);
145
+            return true;
146
+        }
147
+        return false;
148
+    }
149
+
150
+    /**
151
+     * check if recovery is enabled for user
152
+     *
153
+     * @param string $user if no user is given we check the current logged-in user
154
+     *
155
+     * @return bool
156
+     */
157
+    public function isRecoveryEnabledForUser($user = '') {
158
+        $uid = $user === '' ? $this->user->getUID() : $user;
159
+        $recoveryMode = $this->config->getUserValue($uid,
160
+            'encryption',
161
+            'recoveryEnabled',
162
+            0);
163
+
164
+        return ($recoveryMode === '1');
165
+    }
166
+
167
+    /**
168
+     * check if recovery is key is enabled by the administrator
169
+     *
170
+     * @return bool
171
+     */
172
+    public function isRecoveryKeyEnabled() {
173
+        $enabled = $this->config->getAppValue('encryption', 'recoveryAdminEnabled', 0);
174
+
175
+        return ($enabled === '1');
176
+    }
177
+
178
+    /**
179
+     * @param string $value
180
+     * @return bool
181
+     */
182
+    public function setRecoveryForUser($value) {
183
+
184
+        try {
185
+            $this->config->setUserValue($this->user->getUID(),
186
+                'encryption',
187
+                'recoveryEnabled',
188
+                $value);
189
+
190
+            if ($value === '1') {
191
+                $this->addRecoveryKeys('/' . $this->user->getUID() . '/files/');
192
+            } else {
193
+                $this->removeRecoveryKeys('/' . $this->user->getUID() . '/files/');
194
+            }
195
+
196
+            return true;
197
+        } catch (PreConditionNotMetException $e) {
198
+            return false;
199
+        }
200
+    }
201
+
202
+    /**
203
+     * add recovery key to all encrypted files
204
+     * @param string $path
205
+     */
206
+    private function addRecoveryKeys($path) {
207
+        $dirContent = $this->view->getDirectoryContent($path);
208
+        foreach ($dirContent as $item) {
209
+            $filePath = $item->getPath();
210
+            if ($item['type'] === 'dir') {
211
+                $this->addRecoveryKeys($filePath . '/');
212
+            } else {
213
+                $fileKey = $this->keyManager->getFileKey($filePath, $this->user->getUID());
214
+                if (!empty($fileKey)) {
215
+                    $accessList = $this->file->getAccessList($filePath);
216
+                    $publicKeys = [];
217
+                    foreach ($accessList['users'] as $uid) {
218
+                        $publicKeys[$uid] = $this->keyManager->getPublicKey($uid);
219
+                    }
220
+
221
+                    $publicKeys = $this->keyManager->addSystemKeys($accessList, $publicKeys, $this->user->getUID());
222
+
223
+                    $encryptedKeyfiles = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys);
224
+                    $this->keyManager->setAllFileKeys($filePath, $encryptedKeyfiles);
225
+                }
226
+            }
227
+        }
228
+    }
229
+
230
+    /**
231
+     * remove recovery key to all encrypted files
232
+     * @param string $path
233
+     */
234
+    private function removeRecoveryKeys($path) {
235
+        $dirContent = $this->view->getDirectoryContent($path);
236
+        foreach ($dirContent as $item) {
237
+            $filePath = $item->getPath();
238
+            if ($item['type'] === 'dir') {
239
+                $this->removeRecoveryKeys($filePath . '/');
240
+            } else {
241
+                $this->keyManager->deleteShareKey($filePath, $this->keyManager->getRecoveryKeyId());
242
+            }
243
+        }
244
+    }
245
+
246
+    /**
247
+     * recover users files with the recovery key
248
+     *
249
+     * @param string $recoveryPassword
250
+     * @param string $user
251
+     */
252
+    public function recoverUsersFiles($recoveryPassword, $user) {
253
+        $encryptedKey = $this->keyManager->getSystemPrivateKey($this->keyManager->getRecoveryKeyId());
254
+
255
+        $privateKey = $this->crypt->decryptPrivateKey($encryptedKey, $recoveryPassword);
256
+        if($privateKey !== false) {
257
+            $this->recoverAllFiles('/' . $user . '/files/', $privateKey, $user);
258
+        }
259
+    }
260
+
261
+    /**
262
+     * recover users files
263
+     *
264
+     * @param string $path
265
+     * @param string $privateKey
266
+     * @param string $uid
267
+     */
268
+    private function recoverAllFiles($path, $privateKey, $uid) {
269
+        $dirContent = $this->view->getDirectoryContent($path);
270
+
271
+        foreach ($dirContent as $item) {
272
+            // Get relative path from encryption/keyfiles
273
+            $filePath = $item->getPath();
274
+            if ($this->view->is_dir($filePath)) {
275
+                $this->recoverAllFiles($filePath . '/', $privateKey, $uid);
276
+            } else {
277
+                $this->recoverFile($filePath, $privateKey, $uid);
278
+            }
279
+        }
280
+
281
+    }
282
+
283
+    /**
284
+     * recover file
285
+     *
286
+     * @param string $path
287
+     * @param string $privateKey
288
+     * @param string $uid
289
+     */
290
+    private function recoverFile($path, $privateKey, $uid) {
291
+        $encryptedFileKey = $this->keyManager->getEncryptedFileKey($path);
292
+        $shareKey = $this->keyManager->getShareKey($path, $this->keyManager->getRecoveryKeyId());
293
+
294
+        if ($encryptedFileKey && $shareKey && $privateKey) {
295
+            $fileKey = $this->crypt->multiKeyDecrypt($encryptedFileKey,
296
+                $shareKey,
297
+                $privateKey);
298
+        }
299
+
300
+        if (!empty($fileKey)) {
301
+            $accessList = $this->file->getAccessList($path);
302
+            $publicKeys = [];
303
+            foreach ($accessList['users'] as $user) {
304
+                $publicKeys[$user] = $this->keyManager->getPublicKey($user);
305
+            }
306
+
307
+            $publicKeys = $this->keyManager->addSystemKeys($accessList, $publicKeys, $uid);
308
+
309
+            $encryptedKeyfiles = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys);
310
+            $this->keyManager->setAllFileKeys($path, $encryptedKeyfiles);
311
+        }
312
+
313
+    }
314 314
 
315 315
 
316 316
 }
Please login to merge, or discard this patch.
apps/federatedfilesharing/lib/ocm/CloudFederationProviderFiles.php 1 patch
Indentation   +801 added lines, -801 removed lines patch added patch discarded remove patch
@@ -57,805 +57,805 @@
 block discarded – undo
57 57
 
58 58
 class CloudFederationProviderFiles implements ICloudFederationProvider {
59 59
 
60
-	/** @var IAppManager */
61
-	private $appManager;
62
-
63
-	/** @var FederatedShareProvider */
64
-	private $federatedShareProvider;
65
-
66
-	/** @var AddressHandler */
67
-	private $addressHandler;
68
-
69
-	/** @var ILogger */
70
-	private $logger;
71
-
72
-	/** @var IUserManager */
73
-	private $userManager;
74
-
75
-	/** @var IManager */
76
-	private $shareManager;
77
-
78
-	/** @var ICloudIdManager */
79
-	private $cloudIdManager;
80
-
81
-	/** @var IActivityManager */
82
-	private $activityManager;
83
-
84
-	/** @var INotificationManager */
85
-	private $notificationManager;
86
-
87
-	/** @var IURLGenerator */
88
-	private $urlGenerator;
89
-
90
-	/** @var ICloudFederationFactory */
91
-	private $cloudFederationFactory;
92
-
93
-	/** @var ICloudFederationProviderManager */
94
-	private $cloudFederationProviderManager;
95
-
96
-	/** @var IDBConnection */
97
-	private $connection;
98
-
99
-	/** @var IGroupManager */
100
-	private $groupManager;
101
-
102
-	/**
103
-	 * CloudFederationProvider constructor.
104
-	 *
105
-	 * @param IAppManager $appManager
106
-	 * @param FederatedShareProvider $federatedShareProvider
107
-	 * @param AddressHandler $addressHandler
108
-	 * @param ILogger $logger
109
-	 * @param IUserManager $userManager
110
-	 * @param IManager $shareManager
111
-	 * @param ICloudIdManager $cloudIdManager
112
-	 * @param IActivityManager $activityManager
113
-	 * @param INotificationManager $notificationManager
114
-	 * @param IURLGenerator $urlGenerator
115
-	 * @param ICloudFederationFactory $cloudFederationFactory
116
-	 * @param ICloudFederationProviderManager $cloudFederationProviderManager
117
-	 * @param IDBConnection $connection
118
-	 * @param IGroupManager $groupManager
119
-	 */
120
-	public function __construct(IAppManager $appManager,
121
-								FederatedShareProvider $federatedShareProvider,
122
-								AddressHandler $addressHandler,
123
-								ILogger $logger,
124
-								IUserManager $userManager,
125
-								IManager $shareManager,
126
-								ICloudIdManager $cloudIdManager,
127
-								IActivityManager $activityManager,
128
-								INotificationManager $notificationManager,
129
-								IURLGenerator $urlGenerator,
130
-								ICloudFederationFactory $cloudFederationFactory,
131
-								ICloudFederationProviderManager $cloudFederationProviderManager,
132
-								IDBConnection $connection,
133
-								IGroupManager $groupManager
134
-	) {
135
-		$this->appManager = $appManager;
136
-		$this->federatedShareProvider = $federatedShareProvider;
137
-		$this->addressHandler = $addressHandler;
138
-		$this->logger = $logger;
139
-		$this->userManager = $userManager;
140
-		$this->shareManager = $shareManager;
141
-		$this->cloudIdManager = $cloudIdManager;
142
-		$this->activityManager = $activityManager;
143
-		$this->notificationManager = $notificationManager;
144
-		$this->urlGenerator = $urlGenerator;
145
-		$this->cloudFederationFactory = $cloudFederationFactory;
146
-		$this->cloudFederationProviderManager = $cloudFederationProviderManager;
147
-		$this->connection = $connection;
148
-		$this->groupManager = $groupManager;
149
-	}
150
-
151
-
152
-
153
-	/**
154
-	 * @return string
155
-	 */
156
-	public function getShareType() {
157
-		return 'file';
158
-	}
159
-
160
-	/**
161
-	 * share received from another server
162
-	 *
163
-	 * @param ICloudFederationShare $share
164
-	 * @return string provider specific unique ID of the share
165
-	 *
166
-	 * @throws ProviderCouldNotAddShareException
167
-	 * @throws \OCP\AppFramework\QueryException
168
-	 * @throws \OC\HintException
169
-	 * @since 14.0.0
170
-	 */
171
-	public function shareReceived(ICloudFederationShare $share) {
172
-
173
-		if (!$this->isS2SEnabled(true)) {
174
-			throw new ProviderCouldNotAddShareException('Server does not support federated cloud sharing', '', Http::STATUS_SERVICE_UNAVAILABLE);
175
-		}
176
-
177
-		$protocol = $share->getProtocol();
178
-		if ($protocol['name'] !== 'webdav') {
179
-			throw new ProviderCouldNotAddShareException('Unsupported protocol for data exchange.', '', Http::STATUS_NOT_IMPLEMENTED);
180
-		}
181
-
182
-		list($ownerUid, $remote) = $this->addressHandler->splitUserRemote($share->getOwner());
183
-		// for backward compatibility make sure that the remote url stored in the
184
-		// database ends with a trailing slash
185
-		if (substr($remote, -1) !== '/') {
186
-			$remote = $remote . '/';
187
-		}
188
-
189
-		$token = $share->getShareSecret();
190
-		$name = $share->getResourceName();
191
-		$owner = $share->getOwnerDisplayName();
192
-		$sharedBy = $share->getSharedByDisplayName();
193
-		$shareWith = $share->getShareWith();
194
-		$remoteId = $share->getProviderId();
195
-		$sharedByFederatedId = $share->getSharedBy();
196
-		$ownerFederatedId = $share->getOwner();
197
-		$shareType = $this->mapShareTypeToNextcloud($share->getShareType());
198
-
199
-		// if no explicit information about the person who created the share was send
200
-		// we assume that the share comes from the owner
201
-		if ($sharedByFederatedId === null) {
202
-			$sharedBy = $owner;
203
-			$sharedByFederatedId = $ownerFederatedId;
204
-		}
205
-
206
-		if ($remote && $token && $name && $owner && $remoteId && $shareWith) {
207
-
208
-			if (!Util::isValidFileName($name)) {
209
-				throw new ProviderCouldNotAddShareException('The mountpoint name contains invalid characters.', '', Http::STATUS_BAD_REQUEST);
210
-			}
211
-
212
-			// FIXME this should be a method in the user management instead
213
-			if ($shareType === Share::SHARE_TYPE_USER) {
214
-				$this->logger->debug('shareWith before, ' . $shareWith, ['app' => 'files_sharing']);
215
-				Util::emitHook(
216
-					'\OCA\Files_Sharing\API\Server2Server',
217
-					'preLoginNameUsedAsUserName',
218
-					['uid' => &$shareWith]
219
-				);
220
-				$this->logger->debug('shareWith after, ' . $shareWith, ['app' => 'files_sharing']);
221
-
222
-				if (!$this->userManager->userExists($shareWith)) {
223
-					throw new ProviderCouldNotAddShareException('User does not exists', '',Http::STATUS_BAD_REQUEST);
224
-				}
225
-
226
-				\OC_Util::setupFS($shareWith);
227
-			}
228
-
229
-			if ($shareType === Share::SHARE_TYPE_GROUP && !$this->groupManager->groupExists($shareWith)) {
230
-				throw new ProviderCouldNotAddShareException('Group does not exists', '',Http::STATUS_BAD_REQUEST);
231
-			}
232
-
233
-			$externalManager = new \OCA\Files_Sharing\External\Manager(
234
-				\OC::$server->getDatabaseConnection(),
235
-				Filesystem::getMountManager(),
236
-				Filesystem::getLoader(),
237
-				\OC::$server->getHTTPClientService(),
238
-				\OC::$server->getNotificationManager(),
239
-				\OC::$server->query(\OCP\OCS\IDiscoveryService::class),
240
-				\OC::$server->getCloudFederationProviderManager(),
241
-				\OC::$server->getCloudFederationFactory(),
242
-				\OC::$server->getGroupManager(),
243
-				\OC::$server->getUserManager(),
244
-				$shareWith
245
-			);
246
-
247
-			try {
248
-				$externalManager->addShare($remote, $token, '', $name, $owner, $shareType,false, $shareWith, $remoteId);
249
-				$shareId = \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share_external');
250
-
251
-				if ($shareType === Share::SHARE_TYPE_USER) {
252
-					$event = $this->activityManager->generateEvent();
253
-					$event->setApp('files_sharing')
254
-						->setType('remote_share')
255
-						->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/')])
256
-						->setAffectedUser($shareWith)
257
-						->setObject('remote_share', (int)$shareId, $name);
258
-					\OC::$server->getActivityManager()->publish($event);
259
-					$this->notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $sharedBy, $owner);
260
-				} else {
261
-					$groupMembers = $this->groupManager->get($shareWith)->getUsers();
262
-					foreach ($groupMembers as $user) {
263
-						$event = $this->activityManager->generateEvent();
264
-						$event->setApp('files_sharing')
265
-							->setType('remote_share')
266
-							->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/')])
267
-							->setAffectedUser($user->getUID())
268
-							->setObject('remote_share', (int)$shareId, $name);
269
-						\OC::$server->getActivityManager()->publish($event);
270
-						$this->notifyAboutNewShare($user->getUID(), $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $sharedBy, $owner);
271
-					}
272
-				}
273
-				return $shareId;
274
-			} catch (\Exception $e) {
275
-				$this->logger->logException($e, [
276
-					'message' => 'Server can not add remote share.',
277
-					'level' => ILogger::ERROR,
278
-					'app' => 'files_sharing'
279
-				]);
280
-				throw new ProviderCouldNotAddShareException('internal server error, was not able to add share from ' . $remote, '', HTTP::STATUS_INTERNAL_SERVER_ERROR);
281
-			}
282
-		}
283
-
284
-		throw new ProviderCouldNotAddShareException('server can not add remote share, missing parameter', '', HTTP::STATUS_BAD_REQUEST);
285
-
286
-	}
287
-
288
-	/**
289
-	 * notification received from another server
290
-	 *
291
-	 * @param string $notificationType (e.g. SHARE_ACCEPTED)
292
-	 * @param string $providerId id of the share
293
-	 * @param array $notification payload of the notification
294
-	 * @return array data send back to the sender
295
-	 *
296
-	 * @throws ActionNotSupportedException
297
-	 * @throws AuthenticationFailedException
298
-	 * @throws BadRequestException
299
-	 * @throws \OC\HintException
300
-	 * @since 14.0.0
301
-	 */
302
-	public function notificationReceived($notificationType, $providerId, array $notification) {
303
-
304
-		switch ($notificationType) {
305
-			case 'SHARE_ACCEPTED':
306
-				return $this->shareAccepted($providerId, $notification);
307
-			case 'SHARE_DECLINED':
308
-				return $this->shareDeclined($providerId, $notification);
309
-			case 'SHARE_UNSHARED':
310
-				return $this->unshare($providerId, $notification);
311
-			case 'REQUEST_RESHARE':
312
-				return $this->reshareRequested($providerId, $notification);
313
-			case 'RESHARE_UNDO':
314
-				return $this->undoReshare($providerId, $notification);
315
-			case 'RESHARE_CHANGE_PERMISSION':
316
-				return $this->updateResharePermissions($providerId, $notification);
317
-		}
318
-
319
-
320
-		throw new BadRequestException([$notificationType]);
321
-	}
322
-
323
-	/**
324
-	 * map OCM share type (strings) to Nextcloud internal share types (integer)
325
-	 *
326
-	 * @param string $shareType
327
-	 * @return int
328
-	 */
329
-	private function mapShareTypeToNextcloud($shareType) {
330
-		$result = Share::SHARE_TYPE_USER;
331
-		if ($shareType === 'group') {
332
-			$result = Share::SHARE_TYPE_GROUP;
333
-		}
334
-
335
-		return $result;
336
-	}
337
-
338
-	/**
339
-	 * notify user about new federated share
340
-	 *
341
-	 * @param $shareWith
342
-	 * @param $shareId
343
-	 * @param $ownerFederatedId
344
-	 * @param $sharedByFederatedId
345
-	 * @param $name
346
-	 */
347
-	private function notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $sharedBy, $owner) {
348
-		$notification = $this->notificationManager->createNotification();
349
-		$notification->setApp('files_sharing')
350
-			->setUser($shareWith)
351
-			->setDateTime(new \DateTime())
352
-			->setObject('remote_share', $shareId)
353
-			->setSubject('remote_share', [$ownerFederatedId, $sharedByFederatedId, trim($name, '/'), $sharedBy, $owner]);
354
-
355
-		$declineAction = $notification->createAction();
356
-		$declineAction->setLabel('decline')
357
-			->setLink($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'DELETE');
358
-		$notification->addAction($declineAction);
359
-
360
-		$acceptAction = $notification->createAction();
361
-		$acceptAction->setLabel('accept')
362
-			->setLink($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'POST');
363
-		$notification->addAction($acceptAction);
364
-
365
-		$this->notificationManager->notify($notification);
366
-	}
367
-
368
-	/**
369
-	 * process notification that the recipient accepted a share
370
-	 *
371
-	 * @param string $id
372
-	 * @param array $notification
373
-	 * @return array
374
-	 * @throws ActionNotSupportedException
375
-	 * @throws AuthenticationFailedException
376
-	 * @throws BadRequestException
377
-	 * @throws \OC\HintException
378
-	 */
379
-	private function shareAccepted($id, array $notification) {
380
-
381
-		if (!$this->isS2SEnabled()) {
382
-			throw new ActionNotSupportedException('Server does not support federated cloud sharing');
383
-		}
384
-
385
-		if (!isset($notification['sharedSecret'])) {
386
-			throw new BadRequestException(['sharedSecret']);
387
-		}
388
-
389
-		$token = $notification['sharedSecret'];
390
-
391
-		$share = $this->federatedShareProvider->getShareById($id);
392
-
393
-		$this->verifyShare($share, $token);
394
-		$this->executeAcceptShare($share);
395
-		if ($share->getShareOwner() !== $share->getSharedBy()) {
396
-			list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
397
-			$remoteId = $this->federatedShareProvider->getRemoteId($share);
398
-			$notification = $this->cloudFederationFactory->getCloudFederationNotification();
399
-			$notification->setMessage(
400
-				'SHARE_ACCEPTED',
401
-				'file',
402
-				$remoteId,
403
-				[
404
-					'sharedSecret' => $token,
405
-					'message' => 'Recipient accepted the re-share'
406
-				]
407
-
408
-			);
409
-			$this->cloudFederationProviderManager->sendNotification($remote, $notification);
410
-
411
-		}
412
-
413
-		return [];
414
-	}
415
-
416
-	/**
417
-	 * @param IShare $share
418
-	 * @throws ShareNotFound
419
-	 */
420
-	protected function executeAcceptShare(IShare $share) {
421
-		try {
422
-			$fileId = (int)$share->getNode()->getId();
423
-			list($file, $link) = $this->getFile($this->getCorrectUid($share), $fileId);
424
-		} catch (\Exception $e) {
425
-			throw new ShareNotFound();
426
-		}
427
-
428
-		$event = $this->activityManager->generateEvent();
429
-		$event->setApp('files_sharing')
430
-			->setType('remote_share')
431
-			->setAffectedUser($this->getCorrectUid($share))
432
-			->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_ACCEPTED, [$share->getSharedWith(), [$fileId => $file]])
433
-			->setObject('files', $fileId, $file)
434
-			->setLink($link);
435
-		$this->activityManager->publish($event);
436
-	}
437
-
438
-	/**
439
-	 * process notification that the recipient declined a share
440
-	 *
441
-	 * @param string $id
442
-	 * @param array $notification
443
-	 * @return array
444
-	 * @throws ActionNotSupportedException
445
-	 * @throws AuthenticationFailedException
446
-	 * @throws BadRequestException
447
-	 * @throws ShareNotFound
448
-	 * @throws \OC\HintException
449
-	 *
450
-	 */
451
-	protected function shareDeclined($id, array $notification) {
452
-
453
-		if (!$this->isS2SEnabled()) {
454
-			throw new ActionNotSupportedException('Server does not support federated cloud sharing');
455
-		}
456
-
457
-		if (!isset($notification['sharedSecret'])) {
458
-			throw new BadRequestException(['sharedSecret']);
459
-		}
460
-
461
-		$token = $notification['sharedSecret'];
462
-
463
-		$share = $this->federatedShareProvider->getShareById($id);
464
-
465
-		$this->verifyShare($share, $token);
466
-
467
-		if ($share->getShareOwner() !== $share->getSharedBy()) {
468
-			list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
469
-			$remoteId = $this->federatedShareProvider->getRemoteId($share);
470
-			$notification = $this->cloudFederationFactory->getCloudFederationNotification();
471
-			$notification->setMessage(
472
-				'SHARE_DECLINED',
473
-				'file',
474
-				$remoteId,
475
-				[
476
-					'sharedSecret' => $token,
477
-					'message' => 'Recipient declined the re-share'
478
-				]
479
-
480
-			);
481
-			$this->cloudFederationProviderManager->sendNotification($remote, $notification);
482
-		}
483
-
484
-		$this->executeDeclineShare($share);
485
-
486
-		return [];
487
-
488
-	}
489
-
490
-	/**
491
-	 * delete declined share and create a activity
492
-	 *
493
-	 * @param IShare $share
494
-	 * @throws ShareNotFound
495
-	 */
496
-	protected function executeDeclineShare(IShare $share) {
497
-		$this->federatedShareProvider->removeShareFromTable($share);
498
-
499
-		try {
500
-			$fileId = (int)$share->getNode()->getId();
501
-			list($file, $link) = $this->getFile($this->getCorrectUid($share), $fileId);
502
-		} catch (\Exception $e) {
503
-			throw new ShareNotFound();
504
-		}
505
-
506
-		$event = $this->activityManager->generateEvent();
507
-		$event->setApp('files_sharing')
508
-			->setType('remote_share')
509
-			->setAffectedUser($this->getCorrectUid($share))
510
-			->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_DECLINED, [$share->getSharedWith(), [$fileId => $file]])
511
-			->setObject('files', $fileId, $file)
512
-			->setLink($link);
513
-		$this->activityManager->publish($event);
514
-
515
-	}
516
-
517
-	/**
518
-	 * received the notification that the owner unshared a file from you
519
-	 *
520
-	 * @param string $id
521
-	 * @param array $notification
522
-	 * @return array
523
-	 * @throws AuthenticationFailedException
524
-	 * @throws BadRequestException
525
-	 */
526
-	private function undoReshare($id, array $notification) {
527
-		if (!isset($notification['sharedSecret'])) {
528
-			throw new BadRequestException(['sharedSecret']);
529
-		}
530
-		$token = $notification['sharedSecret'];
531
-
532
-		$share = $this->federatedShareProvider->getShareById($id);
533
-
534
-		$this->verifyShare($share, $token);
535
-		$this->federatedShareProvider->removeShareFromTable($share);
536
-		return [];
537
-	}
538
-
539
-	/**
540
-	 * unshare file from self
541
-	 *
542
-	 * @param string $id
543
-	 * @param array $notification
544
-	 * @return array
545
-	 * @throws ActionNotSupportedException
546
-	 * @throws BadRequestException
547
-	 */
548
-	private function unshare($id, array $notification) {
549
-
550
-		if (!$this->isS2SEnabled(true)) {
551
-			throw new ActionNotSupportedException("incoming shares disabled!");
552
-		}
553
-
554
-		if (!isset($notification['sharedSecret'])) {
555
-			throw new BadRequestException(['sharedSecret']);
556
-		}
557
-		$token = $notification['sharedSecret'];
558
-
559
-		$qb = $this->connection->getQueryBuilder();
560
-		$qb->select('*')
561
-			->from('share_external')
562
-			->where(
563
-				$qb->expr()->andX(
564
-					$qb->expr()->eq('remote_id', $qb->createNamedParameter($id)),
565
-					$qb->expr()->eq('share_token', $qb->createNamedParameter($token))
566
-				)
567
-			);
568
-
569
-		$result = $qb->execute();
570
-		$share = $result->fetch();
571
-		$result->closeCursor();
572
-
573
-		if ($token && $id && !empty($share)) {
574
-
575
-			$remote = $this->cleanupRemote($share['remote']);
576
-
577
-			$owner = $this->cloudIdManager->getCloudId($share['owner'], $remote);
578
-			$mountpoint = $share['mountpoint'];
579
-			$user = $share['user'];
580
-
581
-			$qb = $this->connection->getQueryBuilder();
582
-			$qb->delete('share_external')
583
-				->where(
584
-					$qb->expr()->andX(
585
-						$qb->expr()->eq('remote_id', $qb->createNamedParameter($id)),
586
-						$qb->expr()->eq('share_token', $qb->createNamedParameter($token))
587
-					)
588
-				);
589
-
590
-			$qb->execute();
591
-
592
-			// delete all child in case of a group share
593
-			$qb = $this->connection->getQueryBuilder();
594
-			$qb->delete('share_external')
595
-				->where($qb->expr()->eq('parent', $qb->createNamedParameter((int)$share['id'])));
596
-			$qb->execute();
597
-
598
-			if ((int)$share['share_type'] === Share::SHARE_TYPE_USER) {
599
-				if ($share['accepted']) {
600
-					$path = trim($mountpoint, '/');
601
-				} else {
602
-					$path = trim($share['name'], '/');
603
-				}
604
-				$notification = $this->notificationManager->createNotification();
605
-				$notification->setApp('files_sharing')
606
-					->setUser($share['user'])
607
-					->setObject('remote_share', (int)$share['id']);
608
-				$this->notificationManager->markProcessed($notification);
609
-
610
-				$event = $this->activityManager->generateEvent();
611
-				$event->setApp('files_sharing')
612
-					->setType('remote_share')
613
-					->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_UNSHARED, [$owner->getId(), $path])
614
-					->setAffectedUser($user)
615
-					->setObject('remote_share', (int)$share['id'], $path);
616
-				\OC::$server->getActivityManager()->publish($event);
617
-			}
618
-		}
619
-
620
-		return [];
621
-	}
622
-
623
-	private function cleanupRemote($remote) {
624
-		$remote = substr($remote, strpos($remote, '://') + 3);
625
-
626
-		return rtrim($remote, '/');
627
-	}
628
-
629
-	/**
630
-	 * recipient of a share request to re-share the file with another user
631
-	 *
632
-	 * @param string $id
633
-	 * @param array $notification
634
-	 * @return array
635
-	 * @throws AuthenticationFailedException
636
-	 * @throws BadRequestException
637
-	 * @throws ProviderCouldNotAddShareException
638
-	 * @throws ShareNotFound
639
-	 */
640
-	protected function reshareRequested($id, array $notification) {
641
-
642
-		if (!isset($notification['sharedSecret'])) {
643
-			throw new BadRequestException(['sharedSecret']);
644
-		}
645
-		$token = $notification['sharedSecret'];
646
-
647
-		if (!isset($notification['shareWith'])) {
648
-			throw new BadRequestException(['shareWith']);
649
-		}
650
-		$shareWith = $notification['shareWith'];
651
-
652
-		if (!isset($notification['senderId'])) {
653
-			throw new BadRequestException(['senderId']);
654
-		}
655
-		$senderId = $notification['senderId'];
656
-
657
-		$share = $this->federatedShareProvider->getShareById($id);
658
-		// don't allow to share a file back to the owner
659
-		try {
660
-			list($user, $remote) = $this->addressHandler->splitUserRemote($shareWith);
661
-			$owner = $share->getShareOwner();
662
-			$currentServer = $this->addressHandler->generateRemoteURL();
663
-			if ($this->addressHandler->compareAddresses($user, $remote, $owner, $currentServer)) {
664
-				throw new ProviderCouldNotAddShareException('Resharing back to the owner is not allowed: ' . $id);
665
-			}
666
-		} catch (\Exception $e) {
667
-			throw new ProviderCouldNotAddShareException($e->getMessage());
668
-		}
669
-
670
-		$this->verifyShare($share, $token);
671
-
672
-		// check if re-sharing is allowed
673
-		if ($share->getPermissions() & Constants::PERMISSION_SHARE) {
674
-			// the recipient of the initial share is now the initiator for the re-share
675
-			$share->setSharedBy($share->getSharedWith());
676
-			$share->setSharedWith($shareWith);
677
-			$result = $this->federatedShareProvider->create($share);
678
-			$this->federatedShareProvider->storeRemoteId((int)$result->getId(), $senderId);
679
-			return ['token' => $result->getToken(), 'providerId' => $result->getId()];
680
-		} else {
681
-			throw new ProviderCouldNotAddShareException('resharing not allowed for share: ' . $id);
682
-		}
683
-
684
-	}
685
-
686
-	/**
687
-	 * update permission of a re-share so that the share dialog shows the right
688
-	 * permission if the owner or the sender changes the permission
689
-	 *
690
-	 * @param string $id
691
-	 * @param array $notification
692
-	 * @return array
693
-	 * @throws AuthenticationFailedException
694
-	 * @throws BadRequestException
695
-	 */
696
-	protected function updateResharePermissions($id, array $notification) {
697
-
698
-		if (!isset($notification['sharedSecret'])) {
699
-			throw new BadRequestException(['sharedSecret']);
700
-		}
701
-		$token = $notification['sharedSecret'];
702
-
703
-		if (!isset($notification['permission'])) {
704
-			throw new BadRequestException(['permission']);
705
-		}
706
-		$ocmPermissions = $notification['permission'];
707
-
708
-		$share = $this->federatedShareProvider->getShareById($id);
709
-
710
-		$ncPermission = $this->ocmPermissions2ncPermissions($ocmPermissions);
711
-
712
-		$this->verifyShare($share, $token);
713
-		$this->updatePermissionsInDatabase($share, $ncPermission);
714
-
715
-		return [];
716
-	}
717
-
718
-	/**
719
-	 * translate OCM Permissions to Nextcloud permissions
720
-	 *
721
-	 * @param array $ocmPermissions
722
-	 * @return int
723
-	 * @throws BadRequestException
724
-	 */
725
-	protected function ocmPermissions2ncPermissions(array $ocmPermissions) {
726
-		$ncPermissions = 0;
727
-		foreach($ocmPermissions as $permission) {
728
-			switch (strtolower($permission)) {
729
-				case 'read':
730
-					$ncPermissions += Constants::PERMISSION_READ;
731
-					break;
732
-				case 'write':
733
-					$ncPermissions += Constants::PERMISSION_CREATE + Constants::PERMISSION_UPDATE;
734
-					break;
735
-				case 'share':
736
-					$ncPermissions += Constants::PERMISSION_SHARE;
737
-					break;
738
-				default:
739
-					throw new BadRequestException(['permission']);
740
-			}
741
-
742
-		}
743
-
744
-		return $ncPermissions;
745
-	}
746
-
747
-	/**
748
-	 * update permissions in database
749
-	 *
750
-	 * @param IShare $share
751
-	 * @param int $permissions
752
-	 */
753
-	protected function updatePermissionsInDatabase(IShare $share, $permissions) {
754
-		$query = $this->connection->getQueryBuilder();
755
-		$query->update('share')
756
-			->where($query->expr()->eq('id', $query->createNamedParameter($share->getId())))
757
-			->set('permissions', $query->createNamedParameter($permissions))
758
-			->execute();
759
-	}
760
-
761
-
762
-	/**
763
-	 * get file
764
-	 *
765
-	 * @param string $user
766
-	 * @param int $fileSource
767
-	 * @return array with internal path of the file and a absolute link to it
768
-	 */
769
-	private function getFile($user, $fileSource) {
770
-		\OC_Util::setupFS($user);
771
-
772
-		try {
773
-			$file = Filesystem::getPath($fileSource);
774
-		} catch (NotFoundException $e) {
775
-			$file = null;
776
-		}
777
-		$args = Filesystem::is_dir($file) ? ['dir' => $file] : ['dir' => dirname($file), 'scrollto' => $file];
778
-		$link = Util::linkToAbsolute('files', 'index.php', $args);
779
-
780
-		return [$file, $link];
781
-
782
-	}
783
-
784
-	/**
785
-	 * check if we are the initiator or the owner of a re-share and return the correct UID
786
-	 *
787
-	 * @param IShare $share
788
-	 * @return string
789
-	 */
790
-	protected function getCorrectUid(IShare $share) {
791
-		if ($this->userManager->userExists($share->getShareOwner())) {
792
-			return $share->getShareOwner();
793
-		}
794
-
795
-		return $share->getSharedBy();
796
-	}
797
-
798
-
799
-
800
-	/**
801
-	 * check if we got the right share
802
-	 *
803
-	 * @param IShare $share
804
-	 * @param string $token
805
-	 * @return bool
806
-	 * @throws AuthenticationFailedException
807
-	 */
808
-	protected function verifyShare(IShare $share, $token) {
809
-		if (
810
-			$share->getShareType() === FederatedShareProvider::SHARE_TYPE_REMOTE &&
811
-			$share->getToken() === $token
812
-		) {
813
-			return true;
814
-		}
815
-
816
-		if ($share->getShareType() === IShare::TYPE_CIRCLE) {
817
-			try {
818
-				$knownShare = $this->shareManager->getShareByToken($token);
819
-				if ($knownShare->getId() === $share->getId()) {
820
-					return true;
821
-				}
822
-			} catch (ShareNotFound $e) {
823
-			}
824
-		}
825
-
826
-		throw new AuthenticationFailedException();
827
-	}
828
-
829
-
830
-
831
-	/**
832
-	 * check if server-to-server sharing is enabled
833
-	 *
834
-	 * @param bool $incoming
835
-	 * @return bool
836
-	 */
837
-	private function isS2SEnabled($incoming = false) {
838
-
839
-		$result = $this->appManager->isEnabledForUser('files_sharing');
840
-
841
-		if ($incoming) {
842
-			$result = $result && $this->federatedShareProvider->isIncomingServer2serverShareEnabled();
843
-		} else {
844
-			$result = $result && $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
845
-		}
846
-
847
-		return $result;
848
-	}
849
-
850
-
851
-	/**
852
-	 * get the supported share types, e.g. "user", "group", etc.
853
-	 *
854
-	 * @return array
855
-	 *
856
-	 * @since 14.0.0
857
-	 */
858
-	public function getSupportedShareTypes() {
859
-		return ['user', 'group'];
860
-	}
60
+    /** @var IAppManager */
61
+    private $appManager;
62
+
63
+    /** @var FederatedShareProvider */
64
+    private $federatedShareProvider;
65
+
66
+    /** @var AddressHandler */
67
+    private $addressHandler;
68
+
69
+    /** @var ILogger */
70
+    private $logger;
71
+
72
+    /** @var IUserManager */
73
+    private $userManager;
74
+
75
+    /** @var IManager */
76
+    private $shareManager;
77
+
78
+    /** @var ICloudIdManager */
79
+    private $cloudIdManager;
80
+
81
+    /** @var IActivityManager */
82
+    private $activityManager;
83
+
84
+    /** @var INotificationManager */
85
+    private $notificationManager;
86
+
87
+    /** @var IURLGenerator */
88
+    private $urlGenerator;
89
+
90
+    /** @var ICloudFederationFactory */
91
+    private $cloudFederationFactory;
92
+
93
+    /** @var ICloudFederationProviderManager */
94
+    private $cloudFederationProviderManager;
95
+
96
+    /** @var IDBConnection */
97
+    private $connection;
98
+
99
+    /** @var IGroupManager */
100
+    private $groupManager;
101
+
102
+    /**
103
+     * CloudFederationProvider constructor.
104
+     *
105
+     * @param IAppManager $appManager
106
+     * @param FederatedShareProvider $federatedShareProvider
107
+     * @param AddressHandler $addressHandler
108
+     * @param ILogger $logger
109
+     * @param IUserManager $userManager
110
+     * @param IManager $shareManager
111
+     * @param ICloudIdManager $cloudIdManager
112
+     * @param IActivityManager $activityManager
113
+     * @param INotificationManager $notificationManager
114
+     * @param IURLGenerator $urlGenerator
115
+     * @param ICloudFederationFactory $cloudFederationFactory
116
+     * @param ICloudFederationProviderManager $cloudFederationProviderManager
117
+     * @param IDBConnection $connection
118
+     * @param IGroupManager $groupManager
119
+     */
120
+    public function __construct(IAppManager $appManager,
121
+                                FederatedShareProvider $federatedShareProvider,
122
+                                AddressHandler $addressHandler,
123
+                                ILogger $logger,
124
+                                IUserManager $userManager,
125
+                                IManager $shareManager,
126
+                                ICloudIdManager $cloudIdManager,
127
+                                IActivityManager $activityManager,
128
+                                INotificationManager $notificationManager,
129
+                                IURLGenerator $urlGenerator,
130
+                                ICloudFederationFactory $cloudFederationFactory,
131
+                                ICloudFederationProviderManager $cloudFederationProviderManager,
132
+                                IDBConnection $connection,
133
+                                IGroupManager $groupManager
134
+    ) {
135
+        $this->appManager = $appManager;
136
+        $this->federatedShareProvider = $federatedShareProvider;
137
+        $this->addressHandler = $addressHandler;
138
+        $this->logger = $logger;
139
+        $this->userManager = $userManager;
140
+        $this->shareManager = $shareManager;
141
+        $this->cloudIdManager = $cloudIdManager;
142
+        $this->activityManager = $activityManager;
143
+        $this->notificationManager = $notificationManager;
144
+        $this->urlGenerator = $urlGenerator;
145
+        $this->cloudFederationFactory = $cloudFederationFactory;
146
+        $this->cloudFederationProviderManager = $cloudFederationProviderManager;
147
+        $this->connection = $connection;
148
+        $this->groupManager = $groupManager;
149
+    }
150
+
151
+
152
+
153
+    /**
154
+     * @return string
155
+     */
156
+    public function getShareType() {
157
+        return 'file';
158
+    }
159
+
160
+    /**
161
+     * share received from another server
162
+     *
163
+     * @param ICloudFederationShare $share
164
+     * @return string provider specific unique ID of the share
165
+     *
166
+     * @throws ProviderCouldNotAddShareException
167
+     * @throws \OCP\AppFramework\QueryException
168
+     * @throws \OC\HintException
169
+     * @since 14.0.0
170
+     */
171
+    public function shareReceived(ICloudFederationShare $share) {
172
+
173
+        if (!$this->isS2SEnabled(true)) {
174
+            throw new ProviderCouldNotAddShareException('Server does not support federated cloud sharing', '', Http::STATUS_SERVICE_UNAVAILABLE);
175
+        }
176
+
177
+        $protocol = $share->getProtocol();
178
+        if ($protocol['name'] !== 'webdav') {
179
+            throw new ProviderCouldNotAddShareException('Unsupported protocol for data exchange.', '', Http::STATUS_NOT_IMPLEMENTED);
180
+        }
181
+
182
+        list($ownerUid, $remote) = $this->addressHandler->splitUserRemote($share->getOwner());
183
+        // for backward compatibility make sure that the remote url stored in the
184
+        // database ends with a trailing slash
185
+        if (substr($remote, -1) !== '/') {
186
+            $remote = $remote . '/';
187
+        }
188
+
189
+        $token = $share->getShareSecret();
190
+        $name = $share->getResourceName();
191
+        $owner = $share->getOwnerDisplayName();
192
+        $sharedBy = $share->getSharedByDisplayName();
193
+        $shareWith = $share->getShareWith();
194
+        $remoteId = $share->getProviderId();
195
+        $sharedByFederatedId = $share->getSharedBy();
196
+        $ownerFederatedId = $share->getOwner();
197
+        $shareType = $this->mapShareTypeToNextcloud($share->getShareType());
198
+
199
+        // if no explicit information about the person who created the share was send
200
+        // we assume that the share comes from the owner
201
+        if ($sharedByFederatedId === null) {
202
+            $sharedBy = $owner;
203
+            $sharedByFederatedId = $ownerFederatedId;
204
+        }
205
+
206
+        if ($remote && $token && $name && $owner && $remoteId && $shareWith) {
207
+
208
+            if (!Util::isValidFileName($name)) {
209
+                throw new ProviderCouldNotAddShareException('The mountpoint name contains invalid characters.', '', Http::STATUS_BAD_REQUEST);
210
+            }
211
+
212
+            // FIXME this should be a method in the user management instead
213
+            if ($shareType === Share::SHARE_TYPE_USER) {
214
+                $this->logger->debug('shareWith before, ' . $shareWith, ['app' => 'files_sharing']);
215
+                Util::emitHook(
216
+                    '\OCA\Files_Sharing\API\Server2Server',
217
+                    'preLoginNameUsedAsUserName',
218
+                    ['uid' => &$shareWith]
219
+                );
220
+                $this->logger->debug('shareWith after, ' . $shareWith, ['app' => 'files_sharing']);
221
+
222
+                if (!$this->userManager->userExists($shareWith)) {
223
+                    throw new ProviderCouldNotAddShareException('User does not exists', '',Http::STATUS_BAD_REQUEST);
224
+                }
225
+
226
+                \OC_Util::setupFS($shareWith);
227
+            }
228
+
229
+            if ($shareType === Share::SHARE_TYPE_GROUP && !$this->groupManager->groupExists($shareWith)) {
230
+                throw new ProviderCouldNotAddShareException('Group does not exists', '',Http::STATUS_BAD_REQUEST);
231
+            }
232
+
233
+            $externalManager = new \OCA\Files_Sharing\External\Manager(
234
+                \OC::$server->getDatabaseConnection(),
235
+                Filesystem::getMountManager(),
236
+                Filesystem::getLoader(),
237
+                \OC::$server->getHTTPClientService(),
238
+                \OC::$server->getNotificationManager(),
239
+                \OC::$server->query(\OCP\OCS\IDiscoveryService::class),
240
+                \OC::$server->getCloudFederationProviderManager(),
241
+                \OC::$server->getCloudFederationFactory(),
242
+                \OC::$server->getGroupManager(),
243
+                \OC::$server->getUserManager(),
244
+                $shareWith
245
+            );
246
+
247
+            try {
248
+                $externalManager->addShare($remote, $token, '', $name, $owner, $shareType,false, $shareWith, $remoteId);
249
+                $shareId = \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share_external');
250
+
251
+                if ($shareType === Share::SHARE_TYPE_USER) {
252
+                    $event = $this->activityManager->generateEvent();
253
+                    $event->setApp('files_sharing')
254
+                        ->setType('remote_share')
255
+                        ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/')])
256
+                        ->setAffectedUser($shareWith)
257
+                        ->setObject('remote_share', (int)$shareId, $name);
258
+                    \OC::$server->getActivityManager()->publish($event);
259
+                    $this->notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $sharedBy, $owner);
260
+                } else {
261
+                    $groupMembers = $this->groupManager->get($shareWith)->getUsers();
262
+                    foreach ($groupMembers as $user) {
263
+                        $event = $this->activityManager->generateEvent();
264
+                        $event->setApp('files_sharing')
265
+                            ->setType('remote_share')
266
+                            ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/')])
267
+                            ->setAffectedUser($user->getUID())
268
+                            ->setObject('remote_share', (int)$shareId, $name);
269
+                        \OC::$server->getActivityManager()->publish($event);
270
+                        $this->notifyAboutNewShare($user->getUID(), $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $sharedBy, $owner);
271
+                    }
272
+                }
273
+                return $shareId;
274
+            } catch (\Exception $e) {
275
+                $this->logger->logException($e, [
276
+                    'message' => 'Server can not add remote share.',
277
+                    'level' => ILogger::ERROR,
278
+                    'app' => 'files_sharing'
279
+                ]);
280
+                throw new ProviderCouldNotAddShareException('internal server error, was not able to add share from ' . $remote, '', HTTP::STATUS_INTERNAL_SERVER_ERROR);
281
+            }
282
+        }
283
+
284
+        throw new ProviderCouldNotAddShareException('server can not add remote share, missing parameter', '', HTTP::STATUS_BAD_REQUEST);
285
+
286
+    }
287
+
288
+    /**
289
+     * notification received from another server
290
+     *
291
+     * @param string $notificationType (e.g. SHARE_ACCEPTED)
292
+     * @param string $providerId id of the share
293
+     * @param array $notification payload of the notification
294
+     * @return array data send back to the sender
295
+     *
296
+     * @throws ActionNotSupportedException
297
+     * @throws AuthenticationFailedException
298
+     * @throws BadRequestException
299
+     * @throws \OC\HintException
300
+     * @since 14.0.0
301
+     */
302
+    public function notificationReceived($notificationType, $providerId, array $notification) {
303
+
304
+        switch ($notificationType) {
305
+            case 'SHARE_ACCEPTED':
306
+                return $this->shareAccepted($providerId, $notification);
307
+            case 'SHARE_DECLINED':
308
+                return $this->shareDeclined($providerId, $notification);
309
+            case 'SHARE_UNSHARED':
310
+                return $this->unshare($providerId, $notification);
311
+            case 'REQUEST_RESHARE':
312
+                return $this->reshareRequested($providerId, $notification);
313
+            case 'RESHARE_UNDO':
314
+                return $this->undoReshare($providerId, $notification);
315
+            case 'RESHARE_CHANGE_PERMISSION':
316
+                return $this->updateResharePermissions($providerId, $notification);
317
+        }
318
+
319
+
320
+        throw new BadRequestException([$notificationType]);
321
+    }
322
+
323
+    /**
324
+     * map OCM share type (strings) to Nextcloud internal share types (integer)
325
+     *
326
+     * @param string $shareType
327
+     * @return int
328
+     */
329
+    private function mapShareTypeToNextcloud($shareType) {
330
+        $result = Share::SHARE_TYPE_USER;
331
+        if ($shareType === 'group') {
332
+            $result = Share::SHARE_TYPE_GROUP;
333
+        }
334
+
335
+        return $result;
336
+    }
337
+
338
+    /**
339
+     * notify user about new federated share
340
+     *
341
+     * @param $shareWith
342
+     * @param $shareId
343
+     * @param $ownerFederatedId
344
+     * @param $sharedByFederatedId
345
+     * @param $name
346
+     */
347
+    private function notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $sharedBy, $owner) {
348
+        $notification = $this->notificationManager->createNotification();
349
+        $notification->setApp('files_sharing')
350
+            ->setUser($shareWith)
351
+            ->setDateTime(new \DateTime())
352
+            ->setObject('remote_share', $shareId)
353
+            ->setSubject('remote_share', [$ownerFederatedId, $sharedByFederatedId, trim($name, '/'), $sharedBy, $owner]);
354
+
355
+        $declineAction = $notification->createAction();
356
+        $declineAction->setLabel('decline')
357
+            ->setLink($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'DELETE');
358
+        $notification->addAction($declineAction);
359
+
360
+        $acceptAction = $notification->createAction();
361
+        $acceptAction->setLabel('accept')
362
+            ->setLink($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'POST');
363
+        $notification->addAction($acceptAction);
364
+
365
+        $this->notificationManager->notify($notification);
366
+    }
367
+
368
+    /**
369
+     * process notification that the recipient accepted a share
370
+     *
371
+     * @param string $id
372
+     * @param array $notification
373
+     * @return array
374
+     * @throws ActionNotSupportedException
375
+     * @throws AuthenticationFailedException
376
+     * @throws BadRequestException
377
+     * @throws \OC\HintException
378
+     */
379
+    private function shareAccepted($id, array $notification) {
380
+
381
+        if (!$this->isS2SEnabled()) {
382
+            throw new ActionNotSupportedException('Server does not support federated cloud sharing');
383
+        }
384
+
385
+        if (!isset($notification['sharedSecret'])) {
386
+            throw new BadRequestException(['sharedSecret']);
387
+        }
388
+
389
+        $token = $notification['sharedSecret'];
390
+
391
+        $share = $this->federatedShareProvider->getShareById($id);
392
+
393
+        $this->verifyShare($share, $token);
394
+        $this->executeAcceptShare($share);
395
+        if ($share->getShareOwner() !== $share->getSharedBy()) {
396
+            list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
397
+            $remoteId = $this->federatedShareProvider->getRemoteId($share);
398
+            $notification = $this->cloudFederationFactory->getCloudFederationNotification();
399
+            $notification->setMessage(
400
+                'SHARE_ACCEPTED',
401
+                'file',
402
+                $remoteId,
403
+                [
404
+                    'sharedSecret' => $token,
405
+                    'message' => 'Recipient accepted the re-share'
406
+                ]
407
+
408
+            );
409
+            $this->cloudFederationProviderManager->sendNotification($remote, $notification);
410
+
411
+        }
412
+
413
+        return [];
414
+    }
415
+
416
+    /**
417
+     * @param IShare $share
418
+     * @throws ShareNotFound
419
+     */
420
+    protected function executeAcceptShare(IShare $share) {
421
+        try {
422
+            $fileId = (int)$share->getNode()->getId();
423
+            list($file, $link) = $this->getFile($this->getCorrectUid($share), $fileId);
424
+        } catch (\Exception $e) {
425
+            throw new ShareNotFound();
426
+        }
427
+
428
+        $event = $this->activityManager->generateEvent();
429
+        $event->setApp('files_sharing')
430
+            ->setType('remote_share')
431
+            ->setAffectedUser($this->getCorrectUid($share))
432
+            ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_ACCEPTED, [$share->getSharedWith(), [$fileId => $file]])
433
+            ->setObject('files', $fileId, $file)
434
+            ->setLink($link);
435
+        $this->activityManager->publish($event);
436
+    }
437
+
438
+    /**
439
+     * process notification that the recipient declined a share
440
+     *
441
+     * @param string $id
442
+     * @param array $notification
443
+     * @return array
444
+     * @throws ActionNotSupportedException
445
+     * @throws AuthenticationFailedException
446
+     * @throws BadRequestException
447
+     * @throws ShareNotFound
448
+     * @throws \OC\HintException
449
+     *
450
+     */
451
+    protected function shareDeclined($id, array $notification) {
452
+
453
+        if (!$this->isS2SEnabled()) {
454
+            throw new ActionNotSupportedException('Server does not support federated cloud sharing');
455
+        }
456
+
457
+        if (!isset($notification['sharedSecret'])) {
458
+            throw new BadRequestException(['sharedSecret']);
459
+        }
460
+
461
+        $token = $notification['sharedSecret'];
462
+
463
+        $share = $this->federatedShareProvider->getShareById($id);
464
+
465
+        $this->verifyShare($share, $token);
466
+
467
+        if ($share->getShareOwner() !== $share->getSharedBy()) {
468
+            list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
469
+            $remoteId = $this->federatedShareProvider->getRemoteId($share);
470
+            $notification = $this->cloudFederationFactory->getCloudFederationNotification();
471
+            $notification->setMessage(
472
+                'SHARE_DECLINED',
473
+                'file',
474
+                $remoteId,
475
+                [
476
+                    'sharedSecret' => $token,
477
+                    'message' => 'Recipient declined the re-share'
478
+                ]
479
+
480
+            );
481
+            $this->cloudFederationProviderManager->sendNotification($remote, $notification);
482
+        }
483
+
484
+        $this->executeDeclineShare($share);
485
+
486
+        return [];
487
+
488
+    }
489
+
490
+    /**
491
+     * delete declined share and create a activity
492
+     *
493
+     * @param IShare $share
494
+     * @throws ShareNotFound
495
+     */
496
+    protected function executeDeclineShare(IShare $share) {
497
+        $this->federatedShareProvider->removeShareFromTable($share);
498
+
499
+        try {
500
+            $fileId = (int)$share->getNode()->getId();
501
+            list($file, $link) = $this->getFile($this->getCorrectUid($share), $fileId);
502
+        } catch (\Exception $e) {
503
+            throw new ShareNotFound();
504
+        }
505
+
506
+        $event = $this->activityManager->generateEvent();
507
+        $event->setApp('files_sharing')
508
+            ->setType('remote_share')
509
+            ->setAffectedUser($this->getCorrectUid($share))
510
+            ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_DECLINED, [$share->getSharedWith(), [$fileId => $file]])
511
+            ->setObject('files', $fileId, $file)
512
+            ->setLink($link);
513
+        $this->activityManager->publish($event);
514
+
515
+    }
516
+
517
+    /**
518
+     * received the notification that the owner unshared a file from you
519
+     *
520
+     * @param string $id
521
+     * @param array $notification
522
+     * @return array
523
+     * @throws AuthenticationFailedException
524
+     * @throws BadRequestException
525
+     */
526
+    private function undoReshare($id, array $notification) {
527
+        if (!isset($notification['sharedSecret'])) {
528
+            throw new BadRequestException(['sharedSecret']);
529
+        }
530
+        $token = $notification['sharedSecret'];
531
+
532
+        $share = $this->federatedShareProvider->getShareById($id);
533
+
534
+        $this->verifyShare($share, $token);
535
+        $this->federatedShareProvider->removeShareFromTable($share);
536
+        return [];
537
+    }
538
+
539
+    /**
540
+     * unshare file from self
541
+     *
542
+     * @param string $id
543
+     * @param array $notification
544
+     * @return array
545
+     * @throws ActionNotSupportedException
546
+     * @throws BadRequestException
547
+     */
548
+    private function unshare($id, array $notification) {
549
+
550
+        if (!$this->isS2SEnabled(true)) {
551
+            throw new ActionNotSupportedException("incoming shares disabled!");
552
+        }
553
+
554
+        if (!isset($notification['sharedSecret'])) {
555
+            throw new BadRequestException(['sharedSecret']);
556
+        }
557
+        $token = $notification['sharedSecret'];
558
+
559
+        $qb = $this->connection->getQueryBuilder();
560
+        $qb->select('*')
561
+            ->from('share_external')
562
+            ->where(
563
+                $qb->expr()->andX(
564
+                    $qb->expr()->eq('remote_id', $qb->createNamedParameter($id)),
565
+                    $qb->expr()->eq('share_token', $qb->createNamedParameter($token))
566
+                )
567
+            );
568
+
569
+        $result = $qb->execute();
570
+        $share = $result->fetch();
571
+        $result->closeCursor();
572
+
573
+        if ($token && $id && !empty($share)) {
574
+
575
+            $remote = $this->cleanupRemote($share['remote']);
576
+
577
+            $owner = $this->cloudIdManager->getCloudId($share['owner'], $remote);
578
+            $mountpoint = $share['mountpoint'];
579
+            $user = $share['user'];
580
+
581
+            $qb = $this->connection->getQueryBuilder();
582
+            $qb->delete('share_external')
583
+                ->where(
584
+                    $qb->expr()->andX(
585
+                        $qb->expr()->eq('remote_id', $qb->createNamedParameter($id)),
586
+                        $qb->expr()->eq('share_token', $qb->createNamedParameter($token))
587
+                    )
588
+                );
589
+
590
+            $qb->execute();
591
+
592
+            // delete all child in case of a group share
593
+            $qb = $this->connection->getQueryBuilder();
594
+            $qb->delete('share_external')
595
+                ->where($qb->expr()->eq('parent', $qb->createNamedParameter((int)$share['id'])));
596
+            $qb->execute();
597
+
598
+            if ((int)$share['share_type'] === Share::SHARE_TYPE_USER) {
599
+                if ($share['accepted']) {
600
+                    $path = trim($mountpoint, '/');
601
+                } else {
602
+                    $path = trim($share['name'], '/');
603
+                }
604
+                $notification = $this->notificationManager->createNotification();
605
+                $notification->setApp('files_sharing')
606
+                    ->setUser($share['user'])
607
+                    ->setObject('remote_share', (int)$share['id']);
608
+                $this->notificationManager->markProcessed($notification);
609
+
610
+                $event = $this->activityManager->generateEvent();
611
+                $event->setApp('files_sharing')
612
+                    ->setType('remote_share')
613
+                    ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_UNSHARED, [$owner->getId(), $path])
614
+                    ->setAffectedUser($user)
615
+                    ->setObject('remote_share', (int)$share['id'], $path);
616
+                \OC::$server->getActivityManager()->publish($event);
617
+            }
618
+        }
619
+
620
+        return [];
621
+    }
622
+
623
+    private function cleanupRemote($remote) {
624
+        $remote = substr($remote, strpos($remote, '://') + 3);
625
+
626
+        return rtrim($remote, '/');
627
+    }
628
+
629
+    /**
630
+     * recipient of a share request to re-share the file with another user
631
+     *
632
+     * @param string $id
633
+     * @param array $notification
634
+     * @return array
635
+     * @throws AuthenticationFailedException
636
+     * @throws BadRequestException
637
+     * @throws ProviderCouldNotAddShareException
638
+     * @throws ShareNotFound
639
+     */
640
+    protected function reshareRequested($id, array $notification) {
641
+
642
+        if (!isset($notification['sharedSecret'])) {
643
+            throw new BadRequestException(['sharedSecret']);
644
+        }
645
+        $token = $notification['sharedSecret'];
646
+
647
+        if (!isset($notification['shareWith'])) {
648
+            throw new BadRequestException(['shareWith']);
649
+        }
650
+        $shareWith = $notification['shareWith'];
651
+
652
+        if (!isset($notification['senderId'])) {
653
+            throw new BadRequestException(['senderId']);
654
+        }
655
+        $senderId = $notification['senderId'];
656
+
657
+        $share = $this->federatedShareProvider->getShareById($id);
658
+        // don't allow to share a file back to the owner
659
+        try {
660
+            list($user, $remote) = $this->addressHandler->splitUserRemote($shareWith);
661
+            $owner = $share->getShareOwner();
662
+            $currentServer = $this->addressHandler->generateRemoteURL();
663
+            if ($this->addressHandler->compareAddresses($user, $remote, $owner, $currentServer)) {
664
+                throw new ProviderCouldNotAddShareException('Resharing back to the owner is not allowed: ' . $id);
665
+            }
666
+        } catch (\Exception $e) {
667
+            throw new ProviderCouldNotAddShareException($e->getMessage());
668
+        }
669
+
670
+        $this->verifyShare($share, $token);
671
+
672
+        // check if re-sharing is allowed
673
+        if ($share->getPermissions() & Constants::PERMISSION_SHARE) {
674
+            // the recipient of the initial share is now the initiator for the re-share
675
+            $share->setSharedBy($share->getSharedWith());
676
+            $share->setSharedWith($shareWith);
677
+            $result = $this->federatedShareProvider->create($share);
678
+            $this->federatedShareProvider->storeRemoteId((int)$result->getId(), $senderId);
679
+            return ['token' => $result->getToken(), 'providerId' => $result->getId()];
680
+        } else {
681
+            throw new ProviderCouldNotAddShareException('resharing not allowed for share: ' . $id);
682
+        }
683
+
684
+    }
685
+
686
+    /**
687
+     * update permission of a re-share so that the share dialog shows the right
688
+     * permission if the owner or the sender changes the permission
689
+     *
690
+     * @param string $id
691
+     * @param array $notification
692
+     * @return array
693
+     * @throws AuthenticationFailedException
694
+     * @throws BadRequestException
695
+     */
696
+    protected function updateResharePermissions($id, array $notification) {
697
+
698
+        if (!isset($notification['sharedSecret'])) {
699
+            throw new BadRequestException(['sharedSecret']);
700
+        }
701
+        $token = $notification['sharedSecret'];
702
+
703
+        if (!isset($notification['permission'])) {
704
+            throw new BadRequestException(['permission']);
705
+        }
706
+        $ocmPermissions = $notification['permission'];
707
+
708
+        $share = $this->federatedShareProvider->getShareById($id);
709
+
710
+        $ncPermission = $this->ocmPermissions2ncPermissions($ocmPermissions);
711
+
712
+        $this->verifyShare($share, $token);
713
+        $this->updatePermissionsInDatabase($share, $ncPermission);
714
+
715
+        return [];
716
+    }
717
+
718
+    /**
719
+     * translate OCM Permissions to Nextcloud permissions
720
+     *
721
+     * @param array $ocmPermissions
722
+     * @return int
723
+     * @throws BadRequestException
724
+     */
725
+    protected function ocmPermissions2ncPermissions(array $ocmPermissions) {
726
+        $ncPermissions = 0;
727
+        foreach($ocmPermissions as $permission) {
728
+            switch (strtolower($permission)) {
729
+                case 'read':
730
+                    $ncPermissions += Constants::PERMISSION_READ;
731
+                    break;
732
+                case 'write':
733
+                    $ncPermissions += Constants::PERMISSION_CREATE + Constants::PERMISSION_UPDATE;
734
+                    break;
735
+                case 'share':
736
+                    $ncPermissions += Constants::PERMISSION_SHARE;
737
+                    break;
738
+                default:
739
+                    throw new BadRequestException(['permission']);
740
+            }
741
+
742
+        }
743
+
744
+        return $ncPermissions;
745
+    }
746
+
747
+    /**
748
+     * update permissions in database
749
+     *
750
+     * @param IShare $share
751
+     * @param int $permissions
752
+     */
753
+    protected function updatePermissionsInDatabase(IShare $share, $permissions) {
754
+        $query = $this->connection->getQueryBuilder();
755
+        $query->update('share')
756
+            ->where($query->expr()->eq('id', $query->createNamedParameter($share->getId())))
757
+            ->set('permissions', $query->createNamedParameter($permissions))
758
+            ->execute();
759
+    }
760
+
761
+
762
+    /**
763
+     * get file
764
+     *
765
+     * @param string $user
766
+     * @param int $fileSource
767
+     * @return array with internal path of the file and a absolute link to it
768
+     */
769
+    private function getFile($user, $fileSource) {
770
+        \OC_Util::setupFS($user);
771
+
772
+        try {
773
+            $file = Filesystem::getPath($fileSource);
774
+        } catch (NotFoundException $e) {
775
+            $file = null;
776
+        }
777
+        $args = Filesystem::is_dir($file) ? ['dir' => $file] : ['dir' => dirname($file), 'scrollto' => $file];
778
+        $link = Util::linkToAbsolute('files', 'index.php', $args);
779
+
780
+        return [$file, $link];
781
+
782
+    }
783
+
784
+    /**
785
+     * check if we are the initiator or the owner of a re-share and return the correct UID
786
+     *
787
+     * @param IShare $share
788
+     * @return string
789
+     */
790
+    protected function getCorrectUid(IShare $share) {
791
+        if ($this->userManager->userExists($share->getShareOwner())) {
792
+            return $share->getShareOwner();
793
+        }
794
+
795
+        return $share->getSharedBy();
796
+    }
797
+
798
+
799
+
800
+    /**
801
+     * check if we got the right share
802
+     *
803
+     * @param IShare $share
804
+     * @param string $token
805
+     * @return bool
806
+     * @throws AuthenticationFailedException
807
+     */
808
+    protected function verifyShare(IShare $share, $token) {
809
+        if (
810
+            $share->getShareType() === FederatedShareProvider::SHARE_TYPE_REMOTE &&
811
+            $share->getToken() === $token
812
+        ) {
813
+            return true;
814
+        }
815
+
816
+        if ($share->getShareType() === IShare::TYPE_CIRCLE) {
817
+            try {
818
+                $knownShare = $this->shareManager->getShareByToken($token);
819
+                if ($knownShare->getId() === $share->getId()) {
820
+                    return true;
821
+                }
822
+            } catch (ShareNotFound $e) {
823
+            }
824
+        }
825
+
826
+        throw new AuthenticationFailedException();
827
+    }
828
+
829
+
830
+
831
+    /**
832
+     * check if server-to-server sharing is enabled
833
+     *
834
+     * @param bool $incoming
835
+     * @return bool
836
+     */
837
+    private function isS2SEnabled($incoming = false) {
838
+
839
+        $result = $this->appManager->isEnabledForUser('files_sharing');
840
+
841
+        if ($incoming) {
842
+            $result = $result && $this->federatedShareProvider->isIncomingServer2serverShareEnabled();
843
+        } else {
844
+            $result = $result && $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
845
+        }
846
+
847
+        return $result;
848
+    }
849
+
850
+
851
+    /**
852
+     * get the supported share types, e.g. "user", "group", etc.
853
+     *
854
+     * @return array
855
+     *
856
+     * @since 14.0.0
857
+     */
858
+    public function getSupportedShareTypes() {
859
+        return ['user', 'group'];
860
+    }
861 861
 }
Please login to merge, or discard this patch.