Passed
Push — master ( d33b8f...5382ee )
by Roeland
11:29
created

RemoveLinkShares::getShares()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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