Completed
Pull Request — master (#32545)
by Tom
09:21 queued 17s
created

TransferOwnershipService::restoreShares()   B

Complexity

Conditions 7
Paths 24

Size

Total Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 24
nop 0
dl 0
loc 41
rs 8.3306
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Tom Needham <[email protected]>
4
 *
5
 * @copyright Copyright (c) 2018, ownCloud GmbH
6
 * @license AGPL-3.0
7
 *
8
 * This code is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU Affero General Public License, version 3,
10
 * as published by the Free Software Foundation.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License, version 3,
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
19
 *
20
 */
21
22
namespace OCA\Files\Service\TransferOwnership;
23
24
use OC\Encryption\Manager;
25
use OC\Files\Filesystem;
26
use OC\Files\View;
27
use OC\Share20\ProviderFactory;
28
use OCP\Files\FileInfo;
29
use OCP\Files\NotFoundException;
30
use OCP\IConfig;
31
use OCP\ILogger;
32
use OCP\IUser;
33
use OCP\IUserManager;
34
use OCP\Share\IManager;
35
use OCP\Share\IShare;
36
37
class TransferOwnershipService {
38
39
	/** @var bool */
40
	private $filesExist = false;
41
	/** @var bool */
42
	private $foldersExist = false;
43
	/** @var FileInfo[] */
44
	private $encryptedFiles = [];
45
	/** @var IShare[] */
46
	private $shares = [];
47
	/** @var string */
48
	private $sourceUser;
49
	/** @var string */
50
	private $destinationUser;
51
	/** @var string */
52
	private $inputPath;
53
	/** @var string */
54
	private $finalTarget;
55
56
	/** @var Manager  */
57
	protected $encryptionManager;
58
	/** @var IUserManager  */
59
	protected $userManager;
60
	/** @var ILogger */
61
	protected $logger;
62
	/** @var IConfig  */
63
	protected $config;
64
	/** @var ProviderFactory  */
65
	private $shareProviderFactory;
66
	/** @var IManager */
67
	protected $shareManager;
68
69
	public function __construct(
70
		Manager $encryptionManager,
71
		IUserManager $userManager,
72
		ILogger $logger,
73
		IConfig $config,
74
		ProviderFactory $shareProviderFactory,
75
		IManager $shareManager
76
	) {
77
		$this->encryptionManager = $encryptionManager;
78
		$this->userManager = $userManager;
79
		$this->loggger = $logger;
0 ignored issues
show
Bug introduced by
The property loggger does not seem to exist. Did you mean logger?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
80
		$this->config = $config;
81
		$this->shareProviderFactory = $shareProviderFactory;
82
		$this->shareManager = $shareManager;
83
	}
84
85
	public function transfer(IUser $sourceUser, IUser $destinationUser, $sourcePath = '/') {
86
87
		$this->inputPath = $sourcePath;
88
		$this->destinationUser = $destinationUser->getUID();
89
		$this->sourceUser = $sourceUser->getUID();
90
91
		// target user has to be ready
92
		if (!$this->encryptionManager->isReadyForUser($this->destinationUser)) {
93
			throw new \Exception('The target user is not ready to accept files. The user has at least to be logged in once.');
94
		}
95
96
		// use a date format compatible across client OS
97
		$date = \date('Ymd_his');
98
		$this->finalTarget = "$this->destinationUser/files/transferred from $this->sourceUser on $date";
99
100
		// setup filesystem
101
		Filesystem::initMountPoints($this->sourceUser);
102
		Filesystem::initMountPoints($this->destinationUser);
103
104
		if (\strlen($this->inputPath) >= 1) {
105
			$view = new View();
106
			$unknownDir = $this->inputPath;
107
			$this->inputPath = $this->sourceUser . "/files/" . $this->inputPath;
108
			if (!$view->is_dir($this->inputPath)) {
109
				throw new NotFoundException("$unknownDir not found");
110
			}
111
		}
112
113
		// analyse source folder
114
		$this->analyse();
115
116
		if (!$this->filesExist and !$this->foldersExist) {
117
			throw new NotFoundException("No files/folders found for transfer");
118
		}
119
120
		// collect all the shares
121
		$this->collectUsersShares();
122
123
		// transfer the files
124
		$this->doTransfer();
125
126
		// restore the shares
127
		$status = $this->restoreShares();
128
129
		if ($status !== 0) {
130
			throw new \Exception('Failed to complete transfer');
131
		}
132
133
		return true;
134
	}
135
136
	private function walkFiles(View $view, $path, \Closure $callBack) {
137
		foreach ($view->getDirectoryContent($path) as $fileInfo) {
138
			if (!$callBack($fileInfo)) {
139
				return;
140
			}
141
			if ($fileInfo->getType() === FileInfo::TYPE_FOLDER) {
142
				$this->walkFiles($view, $fileInfo->getPath(), $callBack);
143
			}
144
		}
145
	}
146
147
	/**
148
	 * @throws \Exception
149
	 */
150
	protected function analyse() {
151
		$view = new View();
152
		$walkPath = "$this->sourceUser/files";
153
		if (\strlen($this->inputPath) > 0) {
154
			if ($this->inputPath !== "$this->sourceUser/files") {
155
				$walkPath = $this->inputPath;
156
				$this->foldersExist = true;
157
			}
158
		}
159
160
		$this->walkFiles($view, $walkPath,
161
			function (FileInfo $fileInfo) {
162
				if ($fileInfo->getType() === FileInfo::TYPE_FOLDER) {
163
					// only analyze into folders from main storage,
164
					// sub-storages have an empty internal path
165
					if ($fileInfo->getInternalPath() === '' && $fileInfo->getPath() !== '') {
166
						return false;
167
					}
168
169
					$this->foldersExist = true;
170
					return true;
171
				}
172
				$this->filesExist = true;
173
				if ($fileInfo->isEncrypted()) {
174
					if ($this->config->getAppValue('encryption', 'useMasterKey', 0) !== 0) {
175
						/**
176
						 * We are not going to add this to encryptedFiles array.
177
						 * Because its encrypted with masterKey and hence it doesn't
178
						 * require user's specific password.
179
						 */
180
						return true;
181
					}
182
					$this->encryptedFiles[] = $fileInfo;
183
				}
184
				return true;
185
			});
186
187
		// no file is allowed to be encrypted
188
		if (!empty($this->encryptedFiles)) {
189
			throw new \Exception('Encrypted files found - decrypt them first');
190
		}
191
	}
192
193
	/**
194
	 */
195
	private function collectUsersShares() {
196
		foreach ([\OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_LINK, \OCP\Share::SHARE_TYPE_REMOTE] as $shareType) {
197
			$offset = 0;
198
			while (true) {
199
				$sharePage = $this->shareManager->getSharesBy($this->sourceUser, $shareType, null, true, 50, $offset);
200
				if (empty($sharePage)) {
201
					break;
202
				}
203
204
				$this->shares = \array_merge($this->shares, $sharePage);
205
				$offset += 50;
206
			}
207
		}
208
209
	}
210
211
	/**
212
	 */
213
	protected function doTransfer() {
214
		$view = new View();
215
		$sourcePath = (\strlen($this->inputPath) > 0) ? $this->inputPath : "$this->sourceUser/files";
216
		// This change will help user to transfer the folder specified using --path option.
217
		// Else only the content inside folder is transferred which is not correct.
218
		if (\strlen($this->inputPath) > 0) {
219
			if ($this->inputPath !== \ltrim("$this->sourceUser/files", '/')) {
220
				$view->mkdir($this->finalTarget);
221
				$this->finalTarget = $this->finalTarget . '/' . \basename($sourcePath);
222
			}
223
		}
224
225
		$view->rename($sourcePath, $this->finalTarget);
226
227
		if (!\is_dir("$this->sourceUser/files")) {
228
			// because the files folder is moved away we need to recreate it
229
			$view->mkdir("$this->sourceUser/files");
230
		}
231
	}
232
233
	/**
234
	 */
235
	private function restoreShares() {
236
		$status = 0;
237
238
		$childShares = [];
239
		foreach ($this->shares as $share) {
240
			try {
241
				/**
242
				 * Do not process children which are already processed.
243
				 * This piece of code populates the childShare array
244
				 * with the child shares which will be processed. And
245
				 * hence will avoid further processing of same share
246
				 * again.
247
				 */
248
				if ($share->getSharedWith() === $this->destinationUser) {
249
					$provider = $this->shareProviderFactory->getProviderForType($share->getShareType());
250
					foreach ($provider->getChildren($share) as $child) {
251
						$childShares[] = $child->getId();
252
					}
253
				} else {
254
					/**
255
					 * Before doing handover to transferShare, check if the share
256
					 * id is present in the childShares. If so then just ignore
257
					 * this share and continue. If not ignored, the child shares
258
					 * would be processed again, if their parent share was shared
259
					 * with destination user. And hence we can safely avoid the
260
					 * duplicate processing of shares here.
261
					 */
262
					if (\in_array($share->getId(), $childShares, true)) {
263
						continue;
264
					}
265
				}
266
				$this->shareManager->transferShare($share, $this->sourceUser, $this->destinationUser, $this->finalTarget);
267
			} catch (\OCP\Files\NotFoundException $e) {
268
				$this->logger->error('Share with id ' . $share->getId() . ' points at deleted file, skipping');
269
			} catch (\Exception $e) {
270
				$this->logger->error('Could not restore share with id ' . $share->getId() . ':' . $e->getTraceAsString() . '</error>');
271
				$status = 1;
272
			}
273
		}
274
		return $status;
275
	}
276
277
}