RemoveLinkShares.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
  5. *
  6. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  7. * @author Daniel Kesselberg <mail@danielkesselberg.de>
  8. * @author Joas Schilling <coding@schilljs.com>
  9. * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
  10. * @author Roeland Jago Douma <roeland@famdouma.nl>
  11. *
  12. * @license GNU AGPL version 3 or any later version
  13. *
  14. * This program is free software: you can redistribute it and/or modify
  15. * it under the terms of the GNU Affero General Public License as
  16. * published by the Free Software Foundation, either version 3 of the
  17. * License, or (at your option) any later version.
  18. *
  19. * This program is distributed in the hope that it will be useful,
  20. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  22. * GNU Affero General Public License for more details.
  23. *
  24. * You should have received a copy of the GNU Affero General Public License
  25. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  26. *
  27. */
  28. namespace OC\Repair;
  29. use Doctrine\DBAL\Driver\Statement;
  30. use OCP\AppFramework\Utility\ITimeFactory;
  31. use OCP\DB\QueryBuilder\IQueryBuilder;
  32. use OCP\IConfig;
  33. use OCP\IDBConnection;
  34. use OCP\IGroupManager;
  35. use OCP\Migration\IOutput;
  36. use OCP\Migration\IRepairStep;
  37. use OCP\Notification\IManager;
  38. class RemoveLinkShares implements IRepairStep {
  39. /** @var IDBConnection */
  40. private $connection;
  41. /** @var IConfig */
  42. private $config;
  43. /** @var string[] */
  44. private $userToNotify = [];
  45. /** @var IGroupManager */
  46. private $groupManager;
  47. /** @var IManager */
  48. private $notificationManager;
  49. /** @var ITimeFactory */
  50. private $timeFactory;
  51. public function __construct(IDBConnection $connection,
  52. IConfig $config,
  53. IGroupManager $groupManager,
  54. IManager $notificationManager,
  55. ITimeFactory $timeFactory) {
  56. $this->connection = $connection;
  57. $this->config = $config;
  58. $this->groupManager = $groupManager;
  59. $this->notificationManager = $notificationManager;
  60. $this->timeFactory = $timeFactory;
  61. }
  62. public function getName(): string {
  63. return 'Remove potentially over exposing share links';
  64. }
  65. private function shouldRun(): bool {
  66. $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
  67. if (version_compare($versionFromBeforeUpdate, '14.0.11', '<')) {
  68. return true;
  69. }
  70. if (version_compare($versionFromBeforeUpdate, '15.0.8', '<')) {
  71. return true;
  72. }
  73. if (version_compare($versionFromBeforeUpdate, '16.0.0', '<=')) {
  74. return true;
  75. }
  76. return false;
  77. }
  78. /**
  79. * Delete the share
  80. *
  81. * @param int $id
  82. */
  83. private function deleteShare(int $id): void {
  84. $qb = $this->connection->getQueryBuilder();
  85. $qb->delete('share')
  86. ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
  87. $qb->execute();
  88. }
  89. /**
  90. * Get the total of affected shares
  91. *
  92. * @return int
  93. */
  94. private function getTotal(): int {
  95. $subSubQuery = $this->connection->getQueryBuilder();
  96. $subSubQuery->select('*')
  97. ->from('share')
  98. ->where($subSubQuery->expr()->isNotNull('parent'))
  99. ->andWhere($subSubQuery->expr()->eq('share_type', $subSubQuery->expr()->literal(3, IQueryBuilder::PARAM_INT)));
  100. $subQuery = $this->connection->getQueryBuilder();
  101. $subQuery->select('s1.id')
  102. ->from($subQuery->createFunction('(' . $subSubQuery->getSQL() . ')'), 's1')
  103. ->join(
  104. 's1', 'share', 's2',
  105. $subQuery->expr()->eq('s1.parent', 's2.id')
  106. )
  107. ->where($subQuery->expr()->orX(
  108. $subQuery->expr()->eq('s2.share_type', $subQuery->expr()->literal(1, IQueryBuilder::PARAM_INT)),
  109. $subQuery->expr()->eq('s2.share_type', $subQuery->expr()->literal(2, IQueryBuilder::PARAM_INT))
  110. ))
  111. ->andWhere($subQuery->expr()->eq('s1.item_source', 's2.item_source'));
  112. $query = $this->connection->getQueryBuilder();
  113. $query->select($query->func()->count('*', 'total'))
  114. ->from('share')
  115. ->where($query->expr()->in('id', $query->createFunction('(' . $subQuery->getSQL() . ')')));
  116. $result = $query->execute();
  117. $data = $result->fetch();
  118. $result->closeCursor();
  119. return (int) $data['total'];
  120. }
  121. /**
  122. * Get the cursor to fetch all the shares
  123. *
  124. * @return \Doctrine\DBAL\Driver\Statement
  125. */
  126. private function getShares(): Statement {
  127. $subQuery = $this->connection->getQueryBuilder();
  128. $subQuery->select('*')
  129. ->from('share')
  130. ->where($subQuery->expr()->isNotNull('parent'))
  131. ->andWhere($subQuery->expr()->eq('share_type', $subQuery->expr()->literal(3, IQueryBuilder::PARAM_INT)));
  132. $query = $this->connection->getQueryBuilder();
  133. $query->select('s1.id', 's1.uid_owner', 's1.uid_initiator')
  134. ->from($query->createFunction('(' . $subQuery->getSQL() . ')'), 's1')
  135. ->join(
  136. 's1', 'share', 's2',
  137. $query->expr()->eq('s1.parent', 's2.id')
  138. )
  139. ->where($query->expr()->orX(
  140. $query->expr()->eq('s2.share_type', $query->expr()->literal(1, IQueryBuilder::PARAM_INT)),
  141. $query->expr()->eq('s2.share_type', $query->expr()->literal(2, IQueryBuilder::PARAM_INT))
  142. ))
  143. ->andWhere($query->expr()->eq('s1.item_source', 's2.item_source'));
  144. return $query->execute();
  145. }
  146. /**
  147. * Process a single share
  148. *
  149. * @param array $data
  150. */
  151. private function processShare(array $data): void {
  152. $id = $data['id'];
  153. $this->addToNotify($data['uid_owner']);
  154. $this->addToNotify($data['uid_initiator']);
  155. $this->deleteShare((int)$id);
  156. }
  157. /**
  158. * Update list of users to notify
  159. *
  160. * @param string $uid
  161. */
  162. private function addToNotify(string $uid): void {
  163. if (!isset($this->userToNotify[$uid])) {
  164. $this->userToNotify[$uid] = true;
  165. }
  166. }
  167. /**
  168. * Send all notifications
  169. */
  170. private function sendNotification(): void {
  171. $time = $this->timeFactory->getDateTime();
  172. $notification = $this->notificationManager->createNotification();
  173. $notification->setApp('core')
  174. ->setDateTime($time)
  175. ->setObject('repair', 'exposing_links')
  176. ->setSubject('repair_exposing_links');
  177. $users = array_keys($this->userToNotify);
  178. foreach ($users as $user) {
  179. $notification->setUser((string) $user);
  180. $this->notificationManager->notify($notification);
  181. }
  182. }
  183. private function repair(IOutput $output, int $total): void {
  184. $output->startProgress($total);
  185. $shareCursor = $this->getShares();
  186. while ($data = $shareCursor->fetch()) {
  187. $this->processShare($data);
  188. $output->advance();
  189. }
  190. $output->finishProgress();
  191. $shareCursor->closeCursor();
  192. // Notifiy all admins
  193. $adminGroup = $this->groupManager->get('admin');
  194. $adminUsers = $adminGroup->getUsers();
  195. foreach ($adminUsers as $user) {
  196. $this->addToNotify($user->getUID());
  197. }
  198. $output->info('Sending notifications to admins and affected users');
  199. $this->sendNotification();
  200. }
  201. public function run(IOutput $output): void {
  202. if ($this->shouldRun() === false || ($total = $this->getTotal()) === 0) {
  203. $output->info('No need to remove link shares.');
  204. return;
  205. }
  206. $output->info('Removing potentially over exposing link shares');
  207. $this->repair($output, $total);
  208. $output->info('Removed potentially over exposing link shares');
  209. }
  210. }