Passed
Push — master ( e8ae44...61e72d )
by Robin
21:07 queued 12s
created
apps/encryption/lib/Command/FixEncryptedVersion.php 1 patch
Indentation   +282 added lines, -282 removed lines patch added patch discarded remove patch
@@ -36,286 +36,286 @@
 block discarded – undo
36 36
 use Symfony\Component\Console\Output\OutputInterface;
37 37
 
38 38
 class FixEncryptedVersion extends Command {
39
-	/** @var IConfig */
40
-	private $config;
41
-
42
-	/** @var ILogger */
43
-	private $logger;
44
-
45
-	/** @var IRootFolder  */
46
-	private $rootFolder;
47
-
48
-	/** @var IUserManager  */
49
-	private $userManager;
50
-
51
-	/** @var Util */
52
-	private $util;
53
-
54
-	/** @var View  */
55
-	private $view;
56
-
57
-	/** @var bool */
58
-	private $supportLegacy;
59
-
60
-	public function __construct(
61
-		IConfig $config,
62
-		ILogger $logger,
63
-		IRootFolder $rootFolder,
64
-		IUserManager $userManager,
65
-		Util $util,
66
-		View $view
67
-	) {
68
-		$this->config = $config;
69
-		$this->logger = $logger;
70
-		$this->rootFolder = $rootFolder;
71
-		$this->userManager = $userManager;
72
-		$this->util = $util;
73
-		$this->view = $view;
74
-		$this->supportLegacy = false;
75
-
76
-		parent::__construct();
77
-	}
78
-
79
-	protected function configure(): void {
80
-		parent::configure();
81
-
82
-		$this
83
-			->setName('encryption:fix-encrypted-version')
84
-			->setDescription('Fix the encrypted version if the encrypted file(s) are not downloadable.')
85
-			->addArgument(
86
-				'user',
87
-				InputArgument::REQUIRED,
88
-				'The id of the user whose files need fixing'
89
-			)->addOption(
90
-				'path',
91
-				'p',
92
-				InputArgument::OPTIONAL,
93
-				'Limit files to fix with path, e.g., --path="/Music/Artist". If path indicates a directory, all the files inside directory will be fixed.'
94
-			);
95
-	}
96
-
97
-	protected function execute(InputInterface $input, OutputInterface $output): int {
98
-		$skipSignatureCheck = $this->config->getSystemValue('encryption_skip_signature_check', false);
99
-		$this->supportLegacy = $this->config->getSystemValueBool('encryption.legacy_format_support', false);
100
-
101
-		if ($skipSignatureCheck) {
102
-			$output->writeln("<error>Repairing is not possible when \"encryption_skip_signature_check\" is set. Please disable this flag in the configuration.</error>\n");
103
-			return 1;
104
-		}
105
-
106
-		if (!$this->util->isMasterKeyEnabled()) {
107
-			$output->writeln("<error>Repairing only works with master key encryption.</error>\n");
108
-			return 1;
109
-		}
110
-
111
-		$user = (string)$input->getArgument('user');
112
-		$pathToWalk = "/$user/files";
113
-
114
-		$pathOption = \trim(($input->getOption('path') ?? ''), '/');
115
-		if ($pathOption !== "") {
116
-			$pathToWalk = "$pathToWalk/$pathOption";
117
-		}
118
-
119
-		if ($user === '') {
120
-			$output->writeln("<error>No user id provided.</error>\n");
121
-			return 1;
122
-		}
123
-
124
-		if ($this->userManager->get($user) === null) {
125
-			$output->writeln("<error>User id $user does not exist. Please provide a valid user id</error>");
126
-			return 1;
127
-		}
128
-		return $this->walkPathOfUser($user, $pathToWalk, $output);
129
-	}
130
-
131
-	/**
132
-	 * @return int 0 for success, 1 for error
133
-	 */
134
-	private function walkPathOfUser(string $user, string $path, OutputInterface $output): int {
135
-		$this->setupUserFs($user);
136
-		if (!$this->view->file_exists($path)) {
137
-			$output->writeln("<error>Path \"$path\" does not exist. Please provide a valid path.</error>");
138
-			return 1;
139
-		}
140
-
141
-		if ($this->view->is_file($path)) {
142
-			$output->writeln("Verifying the content of file \"$path\"");
143
-			$this->verifyFileContent($path, $output);
144
-			return 0;
145
-		}
146
-		$directories = [];
147
-		$directories[] = $path;
148
-		while ($root = \array_pop($directories)) {
149
-			$directoryContent = $this->view->getDirectoryContent($root);
150
-			foreach ($directoryContent as $file) {
151
-				$path = $root . '/' . $file['name'];
152
-				if ($this->view->is_dir($path)) {
153
-					$directories[] = $path;
154
-				} else {
155
-					$output->writeln("Verifying the content of file \"$path\"");
156
-					$this->verifyFileContent($path, $output);
157
-				}
158
-			}
159
-		}
160
-		return 0;
161
-	}
162
-
163
-	/**
164
-	 * @param bool $ignoreCorrectEncVersionCall, setting this variable to false avoids recursion
165
-	 */
166
-	private function verifyFileContent(string $path, OutputInterface $output, bool $ignoreCorrectEncVersionCall = true): bool {
167
-		try {
168
-			/**
169
-			 * In encryption, the files are read in a block size of 8192 bytes
170
-			 * Read block size of 8192 and a bit more (808 bytes)
171
-			 * If there is any problem, the first block should throw the signature
172
-			 * mismatch error. Which as of now, is enough to proceed ahead to
173
-			 * correct the encrypted version.
174
-			 */
175
-			$handle = $this->view->fopen($path, 'rb');
176
-
177
-			if ($handle === false) {
178
-				$output->writeln("<warning>Failed to open file: \"$path\" skipping</warning>");
179
-				return true;
180
-			}
181
-
182
-			if (\fread($handle, 9001) !== false) {
183
-				$fileInfo = $this->view->getFileInfo($path);
184
-				if (!$fileInfo) {
185
-					$output->writeln("<warning>File info not found for file: \"$path\"</warning>");
186
-					return true;
187
-				}
188
-				$encryptedVersion = $fileInfo->getEncryptedVersion();
189
-				$stat = $this->view->stat($path);
190
-				if (($encryptedVersion == 0) && isset($stat['hasHeader']) && ($stat['hasHeader'] == true)) {
191
-					// The file has encrypted to false but has an encryption header
192
-					if ($ignoreCorrectEncVersionCall === true) {
193
-						// Lets rectify the file by correcting encrypted version
194
-						$output->writeln("<info>Attempting to fix the path: \"$path\"</info>");
195
-						return $this->correctEncryptedVersion($path, $output);
196
-					}
197
-					return false;
198
-				}
199
-				$output->writeln("<info>The file \"$path\" is: OK</info>");
200
-			}
201
-
202
-			\fclose($handle);
203
-
204
-			return true;
205
-		} catch (ServerNotAvailableException $e) {
206
-			// not a "bad signature" error and likely "legacy cipher" exception
207
-			// this could mean that the file is maybe not encrypted but the encrypted version is set
208
-			if (!$this->supportLegacy && $ignoreCorrectEncVersionCall === true) {
209
-				$output->writeln("<info>Attempting to fix the path: \"$path\"</info>");
210
-				return $this->correctEncryptedVersion($path, $output, true);
211
-			}
212
-			return false;
213
-		} catch (HintException $e) {
214
-			$this->logger->warning("Issue: " . $e->getMessage());
215
-			// If allowOnce is set to false, this becomes recursive.
216
-			if ($ignoreCorrectEncVersionCall === true) {
217
-				// Lets rectify the file by correcting encrypted version
218
-				$output->writeln("<info>Attempting to fix the path: \"$path\"</info>");
219
-				return $this->correctEncryptedVersion($path, $output);
220
-			}
221
-			return false;
222
-		}
223
-	}
224
-
225
-	/**
226
-	 * @param bool $includeZero whether to try zero version for unencrypted file
227
-	 */
228
-	private function correctEncryptedVersion(string $path, OutputInterface $output, bool $includeZero = false): bool {
229
-		$fileInfo = $this->view->getFileInfo($path);
230
-		if (!$fileInfo) {
231
-			$output->writeln("<warning>File info not found for file: \"$path\"</warning>");
232
-			return true;
233
-		}
234
-		$fileId = $fileInfo->getId();
235
-		if ($fileId === null) {
236
-			$output->writeln("<warning>File info contains no id for file: \"$path\"</warning>");
237
-			return true;
238
-		}
239
-		$encryptedVersion = $fileInfo->getEncryptedVersion();
240
-		$wrongEncryptedVersion = $encryptedVersion;
241
-
242
-		$storage = $fileInfo->getStorage();
243
-
244
-		$cache = $storage->getCache();
245
-		$fileCache = $cache->get($fileId);
246
-		if (!$fileCache) {
247
-			$output->writeln("<warning>File cache entry not found for file: \"$path\"</warning>");
248
-			return true;
249
-		}
250
-
251
-		if ($storage->instanceOfStorage('OCA\Files_Sharing\ISharedStorage')) {
252
-			$output->writeln("<info>The file: \"$path\" is a share. Please also run the script for the owner of the share</info>");
253
-			return true;
254
-		}
255
-
256
-		// Save original encrypted version so we can restore it if decryption fails with all version
257
-		$originalEncryptedVersion = $encryptedVersion;
258
-		if ($encryptedVersion >= 0) {
259
-			if ($includeZero) {
260
-				// try with zero first
261
-				$cacheInfo = ['encryptedVersion' => 0, 'encrypted' => 0];
262
-				$cache->put($fileCache->getPath(), $cacheInfo);
263
-				$output->writeln("<info>Set the encrypted version to 0 (unencrypted)</info>");
264
-				if ($this->verifyFileContent($path, $output, false) === true) {
265
-					$output->writeln("<info>Fixed the file: \"$path\" with version 0 (unencrypted)</info>");
266
-					return true;
267
-				}
268
-			}
269
-
270
-			// Test by decrementing the value till 1 and if nothing works try incrementing
271
-			$encryptedVersion--;
272
-			while ($encryptedVersion > 0) {
273
-				$cacheInfo = ['encryptedVersion' => $encryptedVersion, 'encrypted' => $encryptedVersion];
274
-				$cache->put($fileCache->getPath(), $cacheInfo);
275
-				$output->writeln("<info>Decrement the encrypted version to $encryptedVersion</info>");
276
-				if ($this->verifyFileContent($path, $output, false) === true) {
277
-					$output->writeln("<info>Fixed the file: \"$path\" with version " . $encryptedVersion . "</info>");
278
-					return true;
279
-				}
280
-				$encryptedVersion--;
281
-			}
282
-
283
-			// So decrementing did not work. Now lets increment. Max increment is till 5
284
-			$increment = 1;
285
-			while ($increment <= 5) {
286
-				/**
287
-				 * The wrongEncryptedVersion would not be incremented so nothing to worry about here.
288
-				 * Only the newEncryptedVersion is incremented.
289
-				 * For example if the wrong encrypted version is 4 then
290
-				 * cycle1 -> newEncryptedVersion = 5 ( 4 + 1)
291
-				 * cycle2 -> newEncryptedVersion = 6 ( 4 + 2)
292
-				 * cycle3 -> newEncryptedVersion = 7 ( 4 + 3)
293
-				 */
294
-				$newEncryptedVersion = $wrongEncryptedVersion + $increment;
295
-
296
-				$cacheInfo = ['encryptedVersion' => $newEncryptedVersion, 'encrypted' => $newEncryptedVersion];
297
-				$cache->put($fileCache->getPath(), $cacheInfo);
298
-				$output->writeln("<info>Increment the encrypted version to $newEncryptedVersion</info>");
299
-				if ($this->verifyFileContent($path, $output, false) === true) {
300
-					$output->writeln("<info>Fixed the file: \"$path\" with version " . $newEncryptedVersion . "</info>");
301
-					return true;
302
-				}
303
-				$increment++;
304
-			}
305
-		}
306
-
307
-		$cacheInfo = ['encryptedVersion' => $originalEncryptedVersion, 'encrypted' => $originalEncryptedVersion];
308
-		$cache->put($fileCache->getPath(), $cacheInfo);
309
-		$output->writeln("<info>No fix found for \"$path\", restored version to original: $originalEncryptedVersion</info>");
310
-
311
-		return false;
312
-	}
313
-
314
-	/**
315
-	 * Setup user file system
316
-	 */
317
-	private function setupUserFs(string $uid): void {
318
-		\OC_Util::tearDownFS();
319
-		\OC_Util::setupFS($uid);
320
-	}
39
+    /** @var IConfig */
40
+    private $config;
41
+
42
+    /** @var ILogger */
43
+    private $logger;
44
+
45
+    /** @var IRootFolder  */
46
+    private $rootFolder;
47
+
48
+    /** @var IUserManager  */
49
+    private $userManager;
50
+
51
+    /** @var Util */
52
+    private $util;
53
+
54
+    /** @var View  */
55
+    private $view;
56
+
57
+    /** @var bool */
58
+    private $supportLegacy;
59
+
60
+    public function __construct(
61
+        IConfig $config,
62
+        ILogger $logger,
63
+        IRootFolder $rootFolder,
64
+        IUserManager $userManager,
65
+        Util $util,
66
+        View $view
67
+    ) {
68
+        $this->config = $config;
69
+        $this->logger = $logger;
70
+        $this->rootFolder = $rootFolder;
71
+        $this->userManager = $userManager;
72
+        $this->util = $util;
73
+        $this->view = $view;
74
+        $this->supportLegacy = false;
75
+
76
+        parent::__construct();
77
+    }
78
+
79
+    protected function configure(): void {
80
+        parent::configure();
81
+
82
+        $this
83
+            ->setName('encryption:fix-encrypted-version')
84
+            ->setDescription('Fix the encrypted version if the encrypted file(s) are not downloadable.')
85
+            ->addArgument(
86
+                'user',
87
+                InputArgument::REQUIRED,
88
+                'The id of the user whose files need fixing'
89
+            )->addOption(
90
+                'path',
91
+                'p',
92
+                InputArgument::OPTIONAL,
93
+                'Limit files to fix with path, e.g., --path="/Music/Artist". If path indicates a directory, all the files inside directory will be fixed.'
94
+            );
95
+    }
96
+
97
+    protected function execute(InputInterface $input, OutputInterface $output): int {
98
+        $skipSignatureCheck = $this->config->getSystemValue('encryption_skip_signature_check', false);
99
+        $this->supportLegacy = $this->config->getSystemValueBool('encryption.legacy_format_support', false);
100
+
101
+        if ($skipSignatureCheck) {
102
+            $output->writeln("<error>Repairing is not possible when \"encryption_skip_signature_check\" is set. Please disable this flag in the configuration.</error>\n");
103
+            return 1;
104
+        }
105
+
106
+        if (!$this->util->isMasterKeyEnabled()) {
107
+            $output->writeln("<error>Repairing only works with master key encryption.</error>\n");
108
+            return 1;
109
+        }
110
+
111
+        $user = (string)$input->getArgument('user');
112
+        $pathToWalk = "/$user/files";
113
+
114
+        $pathOption = \trim(($input->getOption('path') ?? ''), '/');
115
+        if ($pathOption !== "") {
116
+            $pathToWalk = "$pathToWalk/$pathOption";
117
+        }
118
+
119
+        if ($user === '') {
120
+            $output->writeln("<error>No user id provided.</error>\n");
121
+            return 1;
122
+        }
123
+
124
+        if ($this->userManager->get($user) === null) {
125
+            $output->writeln("<error>User id $user does not exist. Please provide a valid user id</error>");
126
+            return 1;
127
+        }
128
+        return $this->walkPathOfUser($user, $pathToWalk, $output);
129
+    }
130
+
131
+    /**
132
+     * @return int 0 for success, 1 for error
133
+     */
134
+    private function walkPathOfUser(string $user, string $path, OutputInterface $output): int {
135
+        $this->setupUserFs($user);
136
+        if (!$this->view->file_exists($path)) {
137
+            $output->writeln("<error>Path \"$path\" does not exist. Please provide a valid path.</error>");
138
+            return 1;
139
+        }
140
+
141
+        if ($this->view->is_file($path)) {
142
+            $output->writeln("Verifying the content of file \"$path\"");
143
+            $this->verifyFileContent($path, $output);
144
+            return 0;
145
+        }
146
+        $directories = [];
147
+        $directories[] = $path;
148
+        while ($root = \array_pop($directories)) {
149
+            $directoryContent = $this->view->getDirectoryContent($root);
150
+            foreach ($directoryContent as $file) {
151
+                $path = $root . '/' . $file['name'];
152
+                if ($this->view->is_dir($path)) {
153
+                    $directories[] = $path;
154
+                } else {
155
+                    $output->writeln("Verifying the content of file \"$path\"");
156
+                    $this->verifyFileContent($path, $output);
157
+                }
158
+            }
159
+        }
160
+        return 0;
161
+    }
162
+
163
+    /**
164
+     * @param bool $ignoreCorrectEncVersionCall, setting this variable to false avoids recursion
165
+     */
166
+    private function verifyFileContent(string $path, OutputInterface $output, bool $ignoreCorrectEncVersionCall = true): bool {
167
+        try {
168
+            /**
169
+             * In encryption, the files are read in a block size of 8192 bytes
170
+             * Read block size of 8192 and a bit more (808 bytes)
171
+             * If there is any problem, the first block should throw the signature
172
+             * mismatch error. Which as of now, is enough to proceed ahead to
173
+             * correct the encrypted version.
174
+             */
175
+            $handle = $this->view->fopen($path, 'rb');
176
+
177
+            if ($handle === false) {
178
+                $output->writeln("<warning>Failed to open file: \"$path\" skipping</warning>");
179
+                return true;
180
+            }
181
+
182
+            if (\fread($handle, 9001) !== false) {
183
+                $fileInfo = $this->view->getFileInfo($path);
184
+                if (!$fileInfo) {
185
+                    $output->writeln("<warning>File info not found for file: \"$path\"</warning>");
186
+                    return true;
187
+                }
188
+                $encryptedVersion = $fileInfo->getEncryptedVersion();
189
+                $stat = $this->view->stat($path);
190
+                if (($encryptedVersion == 0) && isset($stat['hasHeader']) && ($stat['hasHeader'] == true)) {
191
+                    // The file has encrypted to false but has an encryption header
192
+                    if ($ignoreCorrectEncVersionCall === true) {
193
+                        // Lets rectify the file by correcting encrypted version
194
+                        $output->writeln("<info>Attempting to fix the path: \"$path\"</info>");
195
+                        return $this->correctEncryptedVersion($path, $output);
196
+                    }
197
+                    return false;
198
+                }
199
+                $output->writeln("<info>The file \"$path\" is: OK</info>");
200
+            }
201
+
202
+            \fclose($handle);
203
+
204
+            return true;
205
+        } catch (ServerNotAvailableException $e) {
206
+            // not a "bad signature" error and likely "legacy cipher" exception
207
+            // this could mean that the file is maybe not encrypted but the encrypted version is set
208
+            if (!$this->supportLegacy && $ignoreCorrectEncVersionCall === true) {
209
+                $output->writeln("<info>Attempting to fix the path: \"$path\"</info>");
210
+                return $this->correctEncryptedVersion($path, $output, true);
211
+            }
212
+            return false;
213
+        } catch (HintException $e) {
214
+            $this->logger->warning("Issue: " . $e->getMessage());
215
+            // If allowOnce is set to false, this becomes recursive.
216
+            if ($ignoreCorrectEncVersionCall === true) {
217
+                // Lets rectify the file by correcting encrypted version
218
+                $output->writeln("<info>Attempting to fix the path: \"$path\"</info>");
219
+                return $this->correctEncryptedVersion($path, $output);
220
+            }
221
+            return false;
222
+        }
223
+    }
224
+
225
+    /**
226
+     * @param bool $includeZero whether to try zero version for unencrypted file
227
+     */
228
+    private function correctEncryptedVersion(string $path, OutputInterface $output, bool $includeZero = false): bool {
229
+        $fileInfo = $this->view->getFileInfo($path);
230
+        if (!$fileInfo) {
231
+            $output->writeln("<warning>File info not found for file: \"$path\"</warning>");
232
+            return true;
233
+        }
234
+        $fileId = $fileInfo->getId();
235
+        if ($fileId === null) {
236
+            $output->writeln("<warning>File info contains no id for file: \"$path\"</warning>");
237
+            return true;
238
+        }
239
+        $encryptedVersion = $fileInfo->getEncryptedVersion();
240
+        $wrongEncryptedVersion = $encryptedVersion;
241
+
242
+        $storage = $fileInfo->getStorage();
243
+
244
+        $cache = $storage->getCache();
245
+        $fileCache = $cache->get($fileId);
246
+        if (!$fileCache) {
247
+            $output->writeln("<warning>File cache entry not found for file: \"$path\"</warning>");
248
+            return true;
249
+        }
250
+
251
+        if ($storage->instanceOfStorage('OCA\Files_Sharing\ISharedStorage')) {
252
+            $output->writeln("<info>The file: \"$path\" is a share. Please also run the script for the owner of the share</info>");
253
+            return true;
254
+        }
255
+
256
+        // Save original encrypted version so we can restore it if decryption fails with all version
257
+        $originalEncryptedVersion = $encryptedVersion;
258
+        if ($encryptedVersion >= 0) {
259
+            if ($includeZero) {
260
+                // try with zero first
261
+                $cacheInfo = ['encryptedVersion' => 0, 'encrypted' => 0];
262
+                $cache->put($fileCache->getPath(), $cacheInfo);
263
+                $output->writeln("<info>Set the encrypted version to 0 (unencrypted)</info>");
264
+                if ($this->verifyFileContent($path, $output, false) === true) {
265
+                    $output->writeln("<info>Fixed the file: \"$path\" with version 0 (unencrypted)</info>");
266
+                    return true;
267
+                }
268
+            }
269
+
270
+            // Test by decrementing the value till 1 and if nothing works try incrementing
271
+            $encryptedVersion--;
272
+            while ($encryptedVersion > 0) {
273
+                $cacheInfo = ['encryptedVersion' => $encryptedVersion, 'encrypted' => $encryptedVersion];
274
+                $cache->put($fileCache->getPath(), $cacheInfo);
275
+                $output->writeln("<info>Decrement the encrypted version to $encryptedVersion</info>");
276
+                if ($this->verifyFileContent($path, $output, false) === true) {
277
+                    $output->writeln("<info>Fixed the file: \"$path\" with version " . $encryptedVersion . "</info>");
278
+                    return true;
279
+                }
280
+                $encryptedVersion--;
281
+            }
282
+
283
+            // So decrementing did not work. Now lets increment. Max increment is till 5
284
+            $increment = 1;
285
+            while ($increment <= 5) {
286
+                /**
287
+                 * The wrongEncryptedVersion would not be incremented so nothing to worry about here.
288
+                 * Only the newEncryptedVersion is incremented.
289
+                 * For example if the wrong encrypted version is 4 then
290
+                 * cycle1 -> newEncryptedVersion = 5 ( 4 + 1)
291
+                 * cycle2 -> newEncryptedVersion = 6 ( 4 + 2)
292
+                 * cycle3 -> newEncryptedVersion = 7 ( 4 + 3)
293
+                 */
294
+                $newEncryptedVersion = $wrongEncryptedVersion + $increment;
295
+
296
+                $cacheInfo = ['encryptedVersion' => $newEncryptedVersion, 'encrypted' => $newEncryptedVersion];
297
+                $cache->put($fileCache->getPath(), $cacheInfo);
298
+                $output->writeln("<info>Increment the encrypted version to $newEncryptedVersion</info>");
299
+                if ($this->verifyFileContent($path, $output, false) === true) {
300
+                    $output->writeln("<info>Fixed the file: \"$path\" with version " . $newEncryptedVersion . "</info>");
301
+                    return true;
302
+                }
303
+                $increment++;
304
+            }
305
+        }
306
+
307
+        $cacheInfo = ['encryptedVersion' => $originalEncryptedVersion, 'encrypted' => $originalEncryptedVersion];
308
+        $cache->put($fileCache->getPath(), $cacheInfo);
309
+        $output->writeln("<info>No fix found for \"$path\", restored version to original: $originalEncryptedVersion</info>");
310
+
311
+        return false;
312
+    }
313
+
314
+    /**
315
+     * Setup user file system
316
+     */
317
+    private function setupUserFs(string $uid): void {
318
+        \OC_Util::tearDownFS();
319
+        \OC_Util::setupFS($uid);
320
+    }
321 321
 }
Please login to merge, or discard this patch.