From e0ae37745a85cb08fd24f178d588a8659726f907 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Julius=20H=C3=A4rtl?= <jus@bitgrid.net>
Date: Tue, 25 Aug 2020 17:12:27 +0200
Subject: [PATCH] Do not expose direct editing if no master key is available
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Julius Härtl <jus@bitgrid.net>
---
 .../Controller/DirectEditingController.php    | 15 ++++++--
 .../lib/Service/DirectEditingService.php      |  4 +++
 lib/private/DirectEditing/Manager.php         | 35 +++++++++++++++----
 lib/public/DirectEditing/IManager.php         |  8 +++++
 tests/lib/DirectEditing/ManagerTest.php       |  8 ++++-
 5 files changed, 60 insertions(+), 10 deletions(-)

diff --git a/apps/files/lib/Controller/DirectEditingController.php b/apps/files/lib/Controller/DirectEditingController.php
index b29316aff32..a2e765072f2 100644
--- a/apps/files/lib/Controller/DirectEditingController.php
+++ b/apps/files/lib/Controller/DirectEditingController.php
@@ -76,6 +76,9 @@ class DirectEditingController extends OCSController {
 	 * @NoAdminRequired
 	 */
 	public function create(string $path, string $editorId, string $creatorId, string $templateId = null): DataResponse {
+		if (!$this->directEditingManager->isEnabled()) {
+			return new DataResponse(['message' => 'Direct editing is not enabled'], Http::STATUS_INTERNAL_SERVER_ERROR);
+		}
 		$this->eventDispatcher->dispatchTyped(new RegisterDirectEditorEvent($this->directEditingManager));
 
 		try {
@@ -85,7 +88,7 @@ class DirectEditingController extends OCSController {
 			]);
 		} catch (Exception $e) {
 			$this->logger->logException($e, ['message' => 'Exception when creating a new file through direct editing']);
-			return new DataResponse('Failed to create file: ' . $e->getMessage(), Http::STATUS_FORBIDDEN);
+			return new DataResponse(['message' => 'Failed to create file: ' . $e->getMessage()], Http::STATUS_FORBIDDEN);
 		}
 	}
 
@@ -93,6 +96,9 @@ class DirectEditingController extends OCSController {
 	 * @NoAdminRequired
 	 */
 	public function open(string $path, string $editorId = null): DataResponse {
+		if (!$this->directEditingManager->isEnabled()) {
+			return new DataResponse(['message' => 'Direct editing is not enabled'], Http::STATUS_INTERNAL_SERVER_ERROR);
+		}
 		$this->eventDispatcher->dispatchTyped(new RegisterDirectEditorEvent($this->directEditingManager));
 
 		try {
@@ -102,7 +108,7 @@ class DirectEditingController extends OCSController {
 			]);
 		} catch (Exception $e) {
 			$this->logger->logException($e, ['message' => 'Exception when opening a file through direct editing']);
-			return new DataResponse('Failed to open file: ' . $e->getMessage(), Http::STATUS_FORBIDDEN);
+			return new DataResponse(['message' => 'Failed to open file: ' . $e->getMessage()], Http::STATUS_FORBIDDEN);
 		}
 	}
 
@@ -112,13 +118,16 @@ class DirectEditingController extends OCSController {
 	 * @NoAdminRequired
 	 */
 	public function templates(string $editorId, string $creatorId): DataResponse {
+		if (!$this->directEditingManager->isEnabled()) {
+			return new DataResponse(['message' => 'Direct editing is not enabled'], Http::STATUS_INTERNAL_SERVER_ERROR);
+		}
 		$this->eventDispatcher->dispatchTyped(new RegisterDirectEditorEvent($this->directEditingManager));
 
 		try {
 			return new DataResponse($this->directEditingManager->getTemplates($editorId, $creatorId));
 		} catch (Exception $e) {
 			$this->logger->logException($e);
-			return new DataResponse('Failed to obtain template list: ' . $e->getMessage(), Http::STATUS_INTERNAL_SERVER_ERROR);
+			return new DataResponse(['message' => 'Failed to obtain template list: ' . $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
 		}
 	}
 }
diff --git a/apps/files/lib/Service/DirectEditingService.php b/apps/files/lib/Service/DirectEditingService.php
index 91e6a0acbb2..cc9ee54d45e 100644
--- a/apps/files/lib/Service/DirectEditingService.php
+++ b/apps/files/lib/Service/DirectEditingService.php
@@ -55,6 +55,10 @@ class DirectEditingService {
 			'creators' => []
 		];
 
+		if (!$this->directEditingManager->isEnabled()) {
+			return $capabilities;
+		}
+
 		/**
 		 * @var string $id
 		 * @var IEditor $editor
diff --git a/lib/private/DirectEditing/Manager.php b/lib/private/DirectEditing/Manager.php
index c3098fb1a97..3542aeed252 100644
--- a/lib/private/DirectEditing/Manager.php
+++ b/lib/private/DirectEditing/Manager.php
@@ -35,6 +35,7 @@ use OCP\DirectEditing\ACreateFromTemplate;
 use OCP\DirectEditing\IEditor;
 use \OCP\DirectEditing\IManager;
 use OCP\DirectEditing\IToken;
+use OCP\Encryption\IManager as EncryptionManager;
 use OCP\Files\File;
 use OCP\Files\IRootFolder;
 use OCP\Files\Node;
@@ -45,6 +46,7 @@ use OCP\IUserSession;
 use OCP\L10N\IFactory;
 use OCP\Security\ISecureRandom;
 use OCP\Share\IShare;
+use Throwable;
 use function array_key_exists;
 use function in_array;
 
@@ -55,30 +57,33 @@ class Manager implements IManager {
 
 	/** @var IEditor[] */
 	private $editors = [];
-
 	/** @var IDBConnection */
 	private $connection;
-	/**
-	 * @var ISecureRandom
-	 */
+	/** @var ISecureRandom */
 	private $random;
+	/** @var string|null */
 	private $userId;
+	/** @var IRootFolder */
 	private $rootFolder;
 	/** @var IL10N */
 	private $l10n;
+	/** @var EncryptionManager */
+	private $encryptionManager;
 
 	public function __construct(
 		ISecureRandom $random,
 		IDBConnection $connection,
 		IUserSession $userSession,
 		IRootFolder $rootFolder,
-		IFactory $l10nFactory
+		IFactory $l10nFactory,
+		EncryptionManager $encryptionManager
 	) {
 		$this->random = $random;
 		$this->connection = $connection;
 		$this->userId = $userSession->getUser() ? $userSession->getUser()->getUID() : null;
 		$this->rootFolder = $rootFolder;
 		$this->l10n = $l10nFactory->get('core');
+		$this->encryptionManager = $encryptionManager;
 	}
 
 	public function registerDirectEditor(IEditor $directEditor): void {
@@ -171,7 +176,7 @@ class Manager implements IManager {
 			}
 			$editor = $this->getEditor($tokenObject->getEditor());
 			$this->accessToken($token);
-		} catch (\Throwable $throwable) {
+		} catch (Throwable $throwable) {
 			$this->invalidateToken($token);
 			return new NotFoundResponse();
 		}
@@ -275,4 +280,22 @@ class Manager implements IManager {
 		}
 		return $files[0];
 	}
+
+	public function isEnabled(): bool {
+		if (!$this->encryptionManager->isEnabled()) {
+			return true;
+		}
+
+		try {
+			$moduleId = $this->encryptionManager->getDefaultEncryptionModuleId();
+			$module = $this->encryptionManager->getEncryptionModule($moduleId);
+			/** @var \OCA\Encryption\Util $util */
+			$util = \OC::$server->get(\OCA\Encryption\Util::class);
+			if ($module->isReadyForUser($this->userId) && $util->isMasterKeyEnabled()) {
+				return true;
+			}
+		} catch (Throwable $e) {
+		}
+		return false;
+	}
 }
diff --git a/lib/public/DirectEditing/IManager.php b/lib/public/DirectEditing/IManager.php
index bc45d0a45c6..ce39d014193 100644
--- a/lib/public/DirectEditing/IManager.php
+++ b/lib/public/DirectEditing/IManager.php
@@ -84,4 +84,12 @@ interface IManager {
 	 * @return int number of deleted tokens
 	 */
 	public function cleanup(): int;
+
+	/**
+	 * Check if direct editing is enabled
+	 *
+	 * @since 20.0.0
+	 * @return bool
+	 */
+	public function isEnabled(): bool;
 }
diff --git a/tests/lib/DirectEditing/ManagerTest.php b/tests/lib/DirectEditing/ManagerTest.php
index 84685d3a16b..73bb4a836d8 100644
--- a/tests/lib/DirectEditing/ManagerTest.php
+++ b/tests/lib/DirectEditing/ManagerTest.php
@@ -10,6 +10,7 @@ use OCP\AppFramework\Http\Response;
 use OCP\DirectEditing\ACreateEmpty;
 use OCP\DirectEditing\IEditor;
 use OCP\DirectEditing\IToken;
+use OCP\Encryption\IManager;
 use OCP\Files\Folder;
 use OCP\Files\IRootFolder;
 use OCP\IDBConnection;
@@ -104,6 +105,10 @@ class ManagerTest extends TestCase {
 	 * @var MockObject|Folder
 	 */
 	private $userFolder;
+	/**
+	 * @var MockObject|IManager
+	 */
+	private $encryptionManager;
 
 	protected function setUp(): void {
 		parent::setUp();
@@ -116,6 +121,7 @@ class ManagerTest extends TestCase {
 		$this->rootFolder = $this->createMock(IRootFolder::class);
 		$this->userFolder = $this->createMock(Folder::class);
 		$this->l10n = $this->createMock(IL10N::class);
+		$this->encryptionManager = $this->createMock(IManager::class);
 
 		$l10nFactory = $this->createMock(IFactory::class);
 		$l10nFactory->expects($this->once())
@@ -128,7 +134,7 @@ class ManagerTest extends TestCase {
 			->willReturn($this->userFolder);
 
 		$this->manager = new Manager(
-			$this->random, $this->connection, $this->userSession, $this->rootFolder, $l10nFactory
+			$this->random, $this->connection, $this->userSession, $this->rootFolder, $l10nFactory, $this->encryptionManager
 		);
 
 		$this->manager->registerDirectEditor($this->editor);
-- 
GitLab