From e52793c69ef3633f23a93f4358c361431f401569 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Julius=20H=C3=A4rtl?= <jus@bitgrid.net>
Date: Mon, 14 Oct 2019 16:55:39 +0200
Subject: [PATCH] Direct editing API to allow file editing using a one-time
 token for mobile apps
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Julius Härtl <jus@bitgrid.net>
---
 apps/files/appinfo/info.xml                   |   2 +-
 apps/files/appinfo/routes.php                 |  27 ++
 .../composer/composer/autoload_classmap.php   |   3 +
 .../composer/composer/autoload_static.php     |   3 +
 .../Controller/DirectEditingController.php    | 156 ++++++++++++
 .../DirectEditingViewController.php           |  72 ++++++
 .../Version18000Date20191014105105.php        |  95 +++++++
 core/routes.php                               |   3 +-
 lib/composer/composer/autoload_classmap.php   |  10 +
 lib/composer/composer/autoload_static.php     |  10 +
 lib/private/DirectEditing/Manager.php         | 232 ++++++++++++++++++
 lib/private/DirectEditing/Token.php           |  76 ++++++
 lib/private/Server.php                        |   2 +
 lib/public/DirectEditing/ACreateEmpty.php     |  71 ++++++
 .../DirectEditing/ACreateFromTemplate.php     |  39 +++
 lib/public/DirectEditing/ATemplate.php        |  71 ++++++
 lib/public/DirectEditing/IEditor.php          |  99 ++++++++
 lib/public/DirectEditing/IManager.php         |  88 +++++++
 lib/public/DirectEditing/IToken.php           |  77 ++++++
 .../RegisterDirectEditorEvent.php             |  57 +++++
 version.php                                   |   2 +-
 21 files changed, 1192 insertions(+), 3 deletions(-)
 create mode 100644 apps/files/lib/Controller/DirectEditingController.php
 create mode 100644 apps/files/lib/Controller/DirectEditingViewController.php
 create mode 100644 core/Migrations/Version18000Date20191014105105.php
 create mode 100644 lib/private/DirectEditing/Manager.php
 create mode 100644 lib/private/DirectEditing/Token.php
 create mode 100644 lib/public/DirectEditing/ACreateEmpty.php
 create mode 100644 lib/public/DirectEditing/ACreateFromTemplate.php
 create mode 100644 lib/public/DirectEditing/ATemplate.php
 create mode 100644 lib/public/DirectEditing/IEditor.php
 create mode 100644 lib/public/DirectEditing/IManager.php
 create mode 100644 lib/public/DirectEditing/IToken.php
 create mode 100644 lib/public/DirectEditing/RegisterDirectEditorEvent.php

diff --git a/apps/files/appinfo/info.xml b/apps/files/appinfo/info.xml
index 7f3b3a5fcae..d8c61d0f456 100644
--- a/apps/files/appinfo/info.xml
+++ b/apps/files/appinfo/info.xml
@@ -5,7 +5,7 @@
 	<name>Files</name>
 	<summary>File Management</summary>
 	<description>File Management</description>
-	<version>1.13.0</version>
+	<version>1.13.1</version>
 	<licence>agpl</licence>
 	<author>Robin Appelman</author>
 	<author>Vincent Petry</author>
diff --git a/apps/files/appinfo/routes.php b/apps/files/appinfo/routes.php
index 26fce8d1713..6f8a6878aac 100644
--- a/apps/files/appinfo/routes.php
+++ b/apps/files/appinfo/routes.php
@@ -92,6 +92,33 @@ $application->registerRoutes(
 				'url' => '/api/v1/quickaccess/get/NodeType',
 				'verb' => 'GET',
 			],
+			[
+				'name' => 'DirectEditingView#edit',
+				'url' => '/directEditing/{token}',
+				'verb' => 'GET'
+			],
+		],
+		'ocs' => [
+			[
+				'name' => 'DirectEditing#get',
+				'url' => '/api/v1/directEditing',
+				'verb' => 'GET'
+			],
+			[
+				'name' => 'DirectEditing#templates',
+				'url' => '/api/v1/directEditing/templates/{editorId}/{creatorId}',
+				'verb' => 'GET'
+			],
+			[
+				'name' => 'DirectEditing#open',
+				'url' => '/api/v1/directEditing/open',
+				'verb' => 'POST'
+			],
+			[
+				'name' => 'DirectEditing#create',
+				'url' => '/api/v1/directEditing/create',
+				'verb' => 'POST'
+			],
 		]
 	]
 );
diff --git a/apps/files/composer/composer/autoload_classmap.php b/apps/files/composer/composer/autoload_classmap.php
index 6a6584b013f..462e51ffe09 100644
--- a/apps/files/composer/composer/autoload_classmap.php
+++ b/apps/files/composer/composer/autoload_classmap.php
@@ -19,6 +19,7 @@ return array(
     'OCA\\Files\\Activity\\Settings\\FileRestored' => $baseDir . '/../lib/Activity/Settings/FileRestored.php',
     'OCA\\Files\\App' => $baseDir . '/../lib/App.php',
     'OCA\\Files\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
+    'OCA\\Files\\BackgroundJob\\CleanupDirectEditingTokens' => $baseDir . '/../lib/BackgroundJob/CleanupDirectEditingTokens.php',
     'OCA\\Files\\BackgroundJob\\CleanupFileLocks' => $baseDir . '/../lib/BackgroundJob/CleanupFileLocks.php',
     'OCA\\Files\\BackgroundJob\\DeleteOrphanedItems' => $baseDir . '/../lib/BackgroundJob/DeleteOrphanedItems.php',
     'OCA\\Files\\BackgroundJob\\ScanFiles' => $baseDir . '/../lib/BackgroundJob/ScanFiles.php',
@@ -31,6 +32,8 @@ return array(
     'OCA\\Files\\Command\\TransferOwnership' => $baseDir . '/../lib/Command/TransferOwnership.php',
     'OCA\\Files\\Controller\\AjaxController' => $baseDir . '/../lib/Controller/AjaxController.php',
     'OCA\\Files\\Controller\\ApiController' => $baseDir . '/../lib/Controller/ApiController.php',
+    'OCA\\Files\\Controller\\DirectEditingController' => $baseDir . '/../lib/Controller/DirectEditingController.php',
+    'OCA\\Files\\Controller\\DirectEditingViewController' => $baseDir . '/../lib/Controller/DirectEditingViewController.php',
     'OCA\\Files\\Controller\\ViewController' => $baseDir . '/../lib/Controller/ViewController.php',
     'OCA\\Files\\Event\\LoadAdditionalScriptsEvent' => $baseDir . '/../lib/Event/LoadAdditionalScriptsEvent.php',
     'OCA\\Files\\Event\\LoadSidebar' => $baseDir . '/../lib/Event/LoadSidebar.php',
diff --git a/apps/files/composer/composer/autoload_static.php b/apps/files/composer/composer/autoload_static.php
index b1ba7fdc540..b41faa6f8c3 100644
--- a/apps/files/composer/composer/autoload_static.php
+++ b/apps/files/composer/composer/autoload_static.php
@@ -34,6 +34,7 @@ class ComposerStaticInitFiles
         'OCA\\Files\\Activity\\Settings\\FileRestored' => __DIR__ . '/..' . '/../lib/Activity/Settings/FileRestored.php',
         'OCA\\Files\\App' => __DIR__ . '/..' . '/../lib/App.php',
         'OCA\\Files\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
+        'OCA\\Files\\BackgroundJob\\CleanupDirectEditingTokens' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupDirectEditingTokens.php',
         'OCA\\Files\\BackgroundJob\\CleanupFileLocks' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupFileLocks.php',
         'OCA\\Files\\BackgroundJob\\DeleteOrphanedItems' => __DIR__ . '/..' . '/../lib/BackgroundJob/DeleteOrphanedItems.php',
         'OCA\\Files\\BackgroundJob\\ScanFiles' => __DIR__ . '/..' . '/../lib/BackgroundJob/ScanFiles.php',
@@ -46,6 +47,8 @@ class ComposerStaticInitFiles
         'OCA\\Files\\Command\\TransferOwnership' => __DIR__ . '/..' . '/../lib/Command/TransferOwnership.php',
         'OCA\\Files\\Controller\\AjaxController' => __DIR__ . '/..' . '/../lib/Controller/AjaxController.php',
         'OCA\\Files\\Controller\\ApiController' => __DIR__ . '/..' . '/../lib/Controller/ApiController.php',
+        'OCA\\Files\\Controller\\DirectEditingController' => __DIR__ . '/..' . '/../lib/Controller/DirectEditingController.php',
+        'OCA\\Files\\Controller\\DirectEditingViewController' => __DIR__ . '/..' . '/../lib/Controller/DirectEditingViewController.php',
         'OCA\\Files\\Controller\\ViewController' => __DIR__ . '/..' . '/../lib/Controller/ViewController.php',
         'OCA\\Files\\Event\\LoadAdditionalScriptsEvent' => __DIR__ . '/..' . '/../lib/Event/LoadAdditionalScriptsEvent.php',
         'OCA\\Files\\Event\\LoadSidebar' => __DIR__ . '/..' . '/../lib/Event/LoadSidebar.php',
diff --git a/apps/files/lib/Controller/DirectEditingController.php b/apps/files/lib/Controller/DirectEditingController.php
new file mode 100644
index 00000000000..cf948d20f76
--- /dev/null
+++ b/apps/files/lib/Controller/DirectEditingController.php
@@ -0,0 +1,156 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Files\Controller;
+
+
+use Exception;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCSController;
+use OCP\DirectEditing\ACreateEmpty;
+use OCP\DirectEditing\ACreateFromTemplate;
+use OCP\DirectEditing\IEditor;
+use OCP\DirectEditing\IManager;
+use OCP\DirectEditing\RegisterDirectEditorEvent;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\ILogger;
+use OCP\IRequest;
+use OCP\IURLGenerator;
+
+class DirectEditingController extends OCSController {
+
+	/** @var IEventDispatcher */
+	private $eventDispatcher;
+
+	/** @var IManager */
+	private $directEditingManager;
+
+	/** @var IURLGenerator */
+	private $urlGenerator;
+
+	/** @var ILogger */
+	private $logger;
+
+	public function __construct($appName, IRequest $request, $corsMethods, $corsAllowedHeaders, $corsMaxAge,
+								IEventDispatcher $eventDispatcher, IURLGenerator $urlGenerator, IManager $manager, ILogger $logger) {
+		parent::__construct($appName, $request, $corsMethods, $corsAllowedHeaders, $corsMaxAge);
+
+		$this->eventDispatcher = $eventDispatcher;
+		$this->directEditingManager = $manager;
+		$this->logger = $logger;
+		$this->urlGenerator = $urlGenerator;
+	}
+
+	/**
+	 * @NoAdminRequired
+	 *
+	 * @return DataResponse
+	 */
+	public function get(): DataResponse {
+		$this->eventDispatcher->dispatch(RegisterDirectEditorEvent::class, new RegisterDirectEditorEvent($this->directEditingManager));
+
+		$capabilities = [
+			'editors' => [],
+			'creators' => []
+		];
+
+		/**
+		 * @var string $id
+		 * @var IEditor $editor
+		 */
+		foreach ($this->directEditingManager->getEditors() as $id => $editor) {
+			$capabilities['editors'][$id] = [
+				'name' => $editor->getName(),
+				'mimetypes' => $editor->getMimetypes(),
+				'optionalMimetypes' => $editor->getMimetypesOptional(),
+				'secure' => $editor->isSecure(),
+			];
+			/** @var ACreateEmpty|ACreateFromTemplate $creator */
+			foreach ($editor->getCreators() as $creator) {
+				$id = $creator->getId();
+				$capabilities['creators'][$id] = [
+					'id' => $id,
+					'name' => $creator->getName(),
+					'extension' => $creator->getExtension(),
+					'templates' => false
+				];
+				if ($creator instanceof ACreateFromTemplate) {
+					$capabilities['creators'][$id]['templates'] = true;
+				}
+
+			}
+		}
+		return new DataResponse($capabilities);
+	}
+
+	/**
+	 * @NoAdminRequired
+	 */
+	public function create(string $path, string $editorId, string $creatorId, string $templateId = null): DataResponse {
+		$this->eventDispatcher->dispatch(RegisterDirectEditorEvent::class, new RegisterDirectEditorEvent($this->directEditingManager));
+
+		try {
+			$token = $this->directEditingManager->create($path, $editorId, $creatorId, $templateId);
+			return new DataResponse([
+				'url' => $this->urlGenerator->linkToRouteAbsolute('files.DirectEditingView.edit', ['token' => $token])
+			]);
+		} catch (Exception $e) {
+			$this->logger->logException($e, ['message' => 'Exception when creating a new file through direct editing']);
+			return new DataResponse('Failed to create file', Http::STATUS_FORBIDDEN);
+		}
+	}
+
+	/**
+	 * @NoAdminRequired
+	 */
+	public function open(int $fileId, string $editorId = null): DataResponse {
+		$this->eventDispatcher->dispatch(RegisterDirectEditorEvent::class, new RegisterDirectEditorEvent($this->directEditingManager));
+
+		try {
+			$token = $this->directEditingManager->open($fileId, $editorId);
+			return new DataResponse([
+				'url' => $this->urlGenerator->linkToRouteAbsolute('files.DirectEditingView.edit', ['token' => $token])
+			]);
+		} catch (Exception $e) {
+			$this->logger->logException($e, ['message' => 'Exception when opening a file through direct editing']);
+			return new DataResponse('Failed to open file', Http::STATUS_FORBIDDEN);
+		}
+	}
+
+
+
+	/**
+	 * @NoAdminRequired
+	 */
+	public function templates(string $editorId, string $creatorId): DataResponse {
+		$this->eventDispatcher->dispatch(RegisterDirectEditorEvent::class, 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 open file', Http::STATUS_INTERNAL_SERVER_ERROR);
+		}
+	}
+}
diff --git a/apps/files/lib/Controller/DirectEditingViewController.php b/apps/files/lib/Controller/DirectEditingViewController.php
new file mode 100644
index 00000000000..9fbce4ece12
--- /dev/null
+++ b/apps/files/lib/Controller/DirectEditingViewController.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Files\Controller;
+
+
+use Exception;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\NotFoundResponse;
+use OCP\AppFramework\Http\Response;
+use OCP\DirectEditing\IManager;
+use OCP\DirectEditing\RegisterDirectEditorEvent;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\ILogger;
+use OCP\IRequest;
+
+class DirectEditingViewController extends Controller {
+
+	/** @var IEventDispatcher */
+	private $eventDispatcher;
+
+	/** @var IManager */
+	private $directEditingManager;
+
+	/** @var ILogger */
+	private $logger;
+
+	public function __construct($appName, IRequest $request, IEventDispatcher $eventDispatcher, IManager $manager, ILogger $logger) {
+		parent::__construct($appName, $request);
+
+		$this->eventDispatcher = $eventDispatcher;
+		$this->directEditingManager = $manager;
+		$this->logger = $logger;
+	}
+
+	/**
+	 * @PublicPage
+	 * @NoCSRFRequired
+	 *
+	 * @param string $token
+	 * @return Response
+	 */
+	public function edit(string $token): Response {
+		$this->eventDispatcher->dispatchTyped(new RegisterDirectEditorEvent($this->directEditingManager));
+		try {
+			return $this->directEditingManager->edit($token);
+		} catch (Exception $e) {
+			$this->logger->logException($e);
+			return new NotFoundResponse();
+		}
+	}
+}
diff --git a/core/Migrations/Version18000Date20191014105105.php b/core/Migrations/Version18000Date20191014105105.php
new file mode 100644
index 00000000000..b291c0b5e46
--- /dev/null
+++ b/core/Migrations/Version18000Date20191014105105.php
@@ -0,0 +1,95 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Core\Migrations;
+
+use Closure;
+use Doctrine\DBAL\Types\Type;
+use OCP\DB\ISchemaWrapper;
+use OCP\IDBConnection;
+use OCP\Migration\SimpleMigrationStep;
+use OCP\Migration\IOutput;
+
+class Version18000Date20191014105105 extends SimpleMigrationStep {
+
+	/** @var IDBConnection */
+	protected $connection;
+
+	public function __construct(IDBConnection $connection) {
+		$this->connection = $connection;
+	}
+
+	/**
+	 * @param IOutput $output
+	 * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+	 * @param array $options
+	 * @return null|ISchemaWrapper
+	 */
+	public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
+		/** @var ISchemaWrapper $schema */
+		$schema = $schemaClosure();
+		if (!$schema->hasTable('direct_edit')) {
+			$table = $schema->createTable('direct_edit');
+
+			$table->addColumn('id', Type::BIGINT, [
+				'autoincrement' => true,
+				'notnull' => true,
+			]);
+			$table->addColumn('editor_id', Type::STRING, [
+				'notnull' => true,
+				'length' => 64,
+			]);
+			$table->addColumn('token', Type::STRING, [
+				'notnull' => true,
+				'length' => 64,
+			]);
+			$table->addColumn('file_id', Type::BIGINT, [
+				'notnull' => true,
+			]);
+			$table->addColumn('user_id', Type::STRING, [
+				'notnull' => false,
+				'length' => 64,
+			]);
+			$table->addColumn('share_id', Type::BIGINT, [
+				'notnull' => false
+			]);
+			$table->addColumn('timestamp', Type::BIGINT, [
+				'notnull' => true,
+				'length' => 20,
+				'unsigned' => true,
+			]);
+			$table->addColumn('accessed', Type::BOOLEAN, [
+				'notnull' => true,
+				'default' => false
+			]);
+
+			$table->setPrimaryKey(['id']);
+			$table->addIndex(['token']);
+		}
+
+		return $schema;
+	}
+
+}
diff --git a/core/routes.php b/core/routes.php
index 0e6efae59b5..51be391b544 100644
--- a/core/routes.php
+++ b/core/routes.php
@@ -118,7 +118,8 @@ $application->registerRoutes($this, [
 
 		['root' => '/collaboration', 'name' => 'CollaborationResources#removeResource', 'url' => '/resources/collections/{collectionId}', 'verb' => 'DELETE'],
 		['root' => '/collaboration', 'name' => 'CollaborationResources#getCollectionsByResource', 'url' => '/resources/{resourceType}/{resourceId}', 'verb' => 'GET'],
-		['root' => '/collaboration', 'name' => 'CollaborationResources#createCollectionOnResource', 'url' => '/resources/{baseResourceType}/{baseResourceId}', 'verb' => 'POST'],
+		['root' => '/collaboration', 'name' => 'CollaborationResources#createCollectionOnResource', 'url' => '/resources/{baseResourceType}/{baseResourceId}', 'verb' => 'POST']
+
 	],
 ]);
 
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 3d73c7b690d..900a03a0f35 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -170,6 +170,13 @@ return array(
     'OCP\\Diagnostics\\IEventLogger' => $baseDir . '/lib/public/Diagnostics/IEventLogger.php',
     'OCP\\Diagnostics\\IQuery' => $baseDir . '/lib/public/Diagnostics/IQuery.php',
     'OCP\\Diagnostics\\IQueryLogger' => $baseDir . '/lib/public/Diagnostics/IQueryLogger.php',
+    'OCP\\DirectEditing\\ACreateEmpty' => $baseDir . '/lib/public/DirectEditing/ACreateEmpty.php',
+    'OCP\\DirectEditing\\ACreateFromTemplate' => $baseDir . '/lib/public/DirectEditing/ACreateFromTemplate.php',
+    'OCP\\DirectEditing\\ATemplate' => $baseDir . '/lib/public/DirectEditing/ATemplate.php',
+    'OCP\\DirectEditing\\IEditor' => $baseDir . '/lib/public/DirectEditing/IEditor.php',
+    'OCP\\DirectEditing\\IManager' => $baseDir . '/lib/public/DirectEditing/IManager.php',
+    'OCP\\DirectEditing\\IToken' => $baseDir . '/lib/public/DirectEditing/IToken.php',
+    'OCP\\DirectEditing\\RegisterDirectEditorEvent' => $baseDir . '/lib/public/DirectEditing/RegisterDirectEditorEvent.php',
     'OCP\\Encryption\\Exceptions\\GenericEncryptionException' => $baseDir . '/lib/public/Encryption/Exceptions/GenericEncryptionException.php',
     'OCP\\Encryption\\IEncryptionModule' => $baseDir . '/lib/public/Encryption/IEncryptionModule.php',
     'OCP\\Encryption\\IFile' => $baseDir . '/lib/public/Encryption/IFile.php',
@@ -789,6 +796,7 @@ return array(
     'OC\\Core\\Migrations\\Version16000Date20190428150708' => $baseDir . '/core/Migrations/Version16000Date20190428150708.php',
     'OC\\Core\\Migrations\\Version17000Date20190514105811' => $baseDir . '/core/Migrations/Version17000Date20190514105811.php',
     'OC\\Core\\Migrations\\Version18000Date20190920085628' => $baseDir . '/core/Migrations/Version18000Date20190920085628.php',
+    'OC\\Core\\Migrations\\Version18000Date20191014105105' => $baseDir . '/core/Migrations/Version18000Date20191014105105.php',
     'OC\\Core\\Notification\\RemoveLinkSharesNotifier' => $baseDir . '/core/Notification/RemoveLinkSharesNotifier.php',
     'OC\\Core\\Service\\LoginFlowV2Service' => $baseDir . '/core/Service/LoginFlowV2Service.php',
     'OC\\DB\\Adapter' => $baseDir . '/lib/private/DB/Adapter.php',
@@ -840,6 +848,8 @@ return array(
     'OC\\Diagnostics\\EventLogger' => $baseDir . '/lib/private/Diagnostics/EventLogger.php',
     'OC\\Diagnostics\\Query' => $baseDir . '/lib/private/Diagnostics/Query.php',
     'OC\\Diagnostics\\QueryLogger' => $baseDir . '/lib/private/Diagnostics/QueryLogger.php',
+    'OC\\DirectEditing\\Manager' => $baseDir . '/lib/private/DirectEditing/Manager.php',
+    'OC\\DirectEditing\\Token' => $baseDir . '/lib/private/DirectEditing/Token.php',
     'OC\\Encryption\\DecryptAll' => $baseDir . '/lib/private/Encryption/DecryptAll.php',
     'OC\\Encryption\\EncryptionWrapper' => $baseDir . '/lib/private/Encryption/EncryptionWrapper.php',
     'OC\\Encryption\\Exceptions\\DecryptionFailedException' => $baseDir . '/lib/private/Encryption/Exceptions/DecryptionFailedException.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index e00acfbfdb5..84532fabf5f 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -199,6 +199,13 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OCP\\Diagnostics\\IEventLogger' => __DIR__ . '/../../..' . '/lib/public/Diagnostics/IEventLogger.php',
         'OCP\\Diagnostics\\IQuery' => __DIR__ . '/../../..' . '/lib/public/Diagnostics/IQuery.php',
         'OCP\\Diagnostics\\IQueryLogger' => __DIR__ . '/../../..' . '/lib/public/Diagnostics/IQueryLogger.php',
+        'OCP\\DirectEditing\\ACreateEmpty' => __DIR__ . '/../../..' . '/lib/public/DirectEditing/ACreateEmpty.php',
+        'OCP\\DirectEditing\\ACreateFromTemplate' => __DIR__ . '/../../..' . '/lib/public/DirectEditing/ACreateFromTemplate.php',
+        'OCP\\DirectEditing\\ATemplate' => __DIR__ . '/../../..' . '/lib/public/DirectEditing/ATemplate.php',
+        'OCP\\DirectEditing\\IEditor' => __DIR__ . '/../../..' . '/lib/public/DirectEditing/IEditor.php',
+        'OCP\\DirectEditing\\IManager' => __DIR__ . '/../../..' . '/lib/public/DirectEditing/IManager.php',
+        'OCP\\DirectEditing\\IToken' => __DIR__ . '/../../..' . '/lib/public/DirectEditing/IToken.php',
+        'OCP\\DirectEditing\\RegisterDirectEditorEvent' => __DIR__ . '/../../..' . '/lib/public/DirectEditing/RegisterDirectEditorEvent.php',
         'OCP\\Encryption\\Exceptions\\GenericEncryptionException' => __DIR__ . '/../../..' . '/lib/public/Encryption/Exceptions/GenericEncryptionException.php',
         'OCP\\Encryption\\IEncryptionModule' => __DIR__ . '/../../..' . '/lib/public/Encryption/IEncryptionModule.php',
         'OCP\\Encryption\\IFile' => __DIR__ . '/../../..' . '/lib/public/Encryption/IFile.php',
@@ -818,6 +825,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OC\\Core\\Migrations\\Version16000Date20190428150708' => __DIR__ . '/../../..' . '/core/Migrations/Version16000Date20190428150708.php',
         'OC\\Core\\Migrations\\Version17000Date20190514105811' => __DIR__ . '/../../..' . '/core/Migrations/Version17000Date20190514105811.php',
         'OC\\Core\\Migrations\\Version18000Date20190920085628' => __DIR__ . '/../../..' . '/core/Migrations/Version18000Date20190920085628.php',
+        'OC\\Core\\Migrations\\Version18000Date20191014105105' => __DIR__ . '/../../..' . '/core/Migrations/Version18000Date20191014105105.php',
         'OC\\Core\\Notification\\RemoveLinkSharesNotifier' => __DIR__ . '/../../..' . '/core/Notification/RemoveLinkSharesNotifier.php',
         'OC\\Core\\Service\\LoginFlowV2Service' => __DIR__ . '/../../..' . '/core/Service/LoginFlowV2Service.php',
         'OC\\DB\\Adapter' => __DIR__ . '/../../..' . '/lib/private/DB/Adapter.php',
@@ -869,6 +877,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OC\\Diagnostics\\EventLogger' => __DIR__ . '/../../..' . '/lib/private/Diagnostics/EventLogger.php',
         'OC\\Diagnostics\\Query' => __DIR__ . '/../../..' . '/lib/private/Diagnostics/Query.php',
         'OC\\Diagnostics\\QueryLogger' => __DIR__ . '/../../..' . '/lib/private/Diagnostics/QueryLogger.php',
+        'OC\\DirectEditing\\Manager' => __DIR__ . '/../../..' . '/lib/private/DirectEditing/Manager.php',
+        'OC\\DirectEditing\\Token' => __DIR__ . '/../../..' . '/lib/private/DirectEditing/Token.php',
         'OC\\Encryption\\DecryptAll' => __DIR__ . '/../../..' . '/lib/private/Encryption/DecryptAll.php',
         'OC\\Encryption\\EncryptionWrapper' => __DIR__ . '/../../..' . '/lib/private/Encryption/EncryptionWrapper.php',
         'OC\\Encryption\\Exceptions\\DecryptionFailedException' => __DIR__ . '/../../..' . '/lib/private/Encryption/Exceptions/DecryptionFailedException.php',
diff --git a/lib/private/DirectEditing/Manager.php b/lib/private/DirectEditing/Manager.php
new file mode 100644
index 00000000000..085a7de557a
--- /dev/null
+++ b/lib/private/DirectEditing/Manager.php
@@ -0,0 +1,232 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\DirectEditing;
+
+use Doctrine\DBAL\FetchMode;
+use OCP\AppFramework\Http\NotFoundResponse;
+use OCP\AppFramework\Http\Response;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\DirectEditing\ACreateFromTemplate;
+use OCP\DirectEditing\IEditor;
+use \OCP\DirectEditing\IManager;
+use OCP\DirectEditing\IToken;
+use OCP\Files\File;
+use OCP\Files\IRootFolder;
+use OCP\Files\NotFoundException;
+use OCP\IDBConnection;
+use OCP\IUserSession;
+use OCP\Security\ISecureRandom;
+use OCP\Share\IShare;
+
+class Manager implements IManager {
+
+	private const TOKEN_CLEANUP_TIME = 12 * 60 * 60 ;
+
+	public const TABLE_TOKENS = 'direct_edit';
+
+	/** @var IEditor[] */
+	private $editors;
+
+	/** @var IDBConnection */
+	private $connection;
+	/**
+	 * @var ISecureRandom
+	 */
+	private $random;
+	private $userId;
+	private $rootFolder;
+
+	public function __construct(
+		ISecureRandom $random,
+		IDBConnection $connection,
+		IUserSession $userSession,
+		IRootFolder $rootFolder
+	) {
+		$this->random = $random;
+		$this->connection = $connection;
+		$this->userId = $userSession->getUser() ? $userSession->getUser()->getUID() : null;
+		$this->rootFolder = $rootFolder;
+	}
+
+	public function registerDirectEditor(IEditor $directEditor): void {
+		$this->editors[$directEditor->getId()] = $directEditor;
+	}
+
+	public function getEditors(): array {
+		return $this->editors;
+	}
+
+	public function getTemplates(string $editor, string $type): array {
+		if (!array_key_exists($editor, $this->editors)) {
+			throw new \RuntimeException('No matching editor found');
+		}
+		$templates = [];
+		foreach ($this->editors[$editor]->getCreators() as $creator) {
+			if ($creator instanceof ACreateFromTemplate && $creator->getId() === $type) {
+				$templates = $creator->getTemplates();
+			}
+		}
+		return $templates;
+	}
+
+	public function create(string $path, string $editorId, string $creatorId, $templateId = null): string {
+		$userFolder = $this->rootFolder->getUserFolder($this->userId);
+		$file = $userFolder->newFile($path);
+		$editor = $this->getEditor($editorId);
+		$creators = $editor->getCreators();
+		foreach ($creators as $creator) {
+			if ($creator->getId() === $creatorId) {
+				$creator->create($file, $creatorId, $templateId);
+				return $this->createToken($editorId, $file);
+			}
+		}
+		throw new \RuntimeException('No creator found');
+	}
+
+	public function open(int $fileId, string $editorId = null): string {
+		$file = $this->rootFolder->getUserFolder($this->userId)->getById($fileId);
+		if (count($file) === 0 || !($file[0] instanceof File) || $file === null) {
+			throw new NotFoundException();
+		}
+		/** @var File $file */
+		$file = $file[0];
+
+		if ($editorId === null) {
+			$editorId = $this->findEditorForFile($file);
+		}
+
+		return $this->createToken($editorId, $file);
+	}
+
+	private function findEditorForFile(File $file) {
+		foreach ($this->editors as $editor) {
+			if (in_array($file->getMimeType(), $editor->getMimetypes())) {
+				return $editor->getId();
+			}
+		}
+		throw new \RuntimeException('No default editor found for files mimetype');
+	}
+
+	public function edit(string $token): Response {
+		try {
+			/** @var IEditor $editor */
+			$tokenObject = $this->getToken($token);
+			if ($tokenObject->hasBeenAccessed()) {
+				throw new \RuntimeException('Token has already been used and can only be used for followup requests');
+			}
+			$editor = $this->getEditor($tokenObject->getEditor());
+			$this->accessToken($token);
+
+		} catch (\Throwable $throwable) {
+			$this->invalidateToken($token);
+			return new NotFoundResponse();
+		}
+		return $editor->open($tokenObject);
+	}
+
+	public function editSecure(File $file, string $editorId): TemplateResponse {
+		// TODO: Implementation in follow up
+	}
+
+	private function getEditor($editorId): IEditor {
+		if (!array_key_exists($editorId, $this->editors)) {
+			throw new \RuntimeException('No editor found');
+		}
+		return $this->editors[$editorId];
+	}
+
+	public function getToken(string $token): IToken {
+		$query = $this->connection->getQueryBuilder();
+		$query->select('*')->from(self::TABLE_TOKENS)
+			->where($query->expr()->eq('token', $query->createNamedParameter($token, IQueryBuilder::PARAM_STR)));
+		$result = $query->execute();
+		if ($tokenRow = $result->fetch(FetchMode::ASSOCIATIVE)) {
+			return new Token($this, $tokenRow);
+		}
+		throw new \RuntimeException('Failed to validate the token');
+	}
+
+	public function cleanup(): int {
+		$query = $this->connection->getQueryBuilder();
+		$query->delete(self::TABLE_TOKENS)
+			->where($query->expr()->lt('timestamp', $query->createNamedParameter(time() - self::TOKEN_CLEANUP_TIME)));
+		return $query->execute();
+	}
+
+	public function refreshToken(string $token): bool {
+		$query = $this->connection->getQueryBuilder();
+		$query->update(self::TABLE_TOKENS)
+			->set('timestamp', $query->createNamedParameter(time(), IQueryBuilder::PARAM_INT))
+			->where($query->expr()->eq('token', $query->createNamedParameter($token, IQueryBuilder::PARAM_STR)));
+		$result = $query->execute();
+		return $result !== 0;
+	}
+
+
+	public function invalidateToken(string $token): bool {
+		$query = $this->connection->getQueryBuilder();
+		$query->delete(self::TABLE_TOKENS)
+			->where($query->expr()->eq('token', $query->createNamedParameter($token, IQueryBuilder::PARAM_STR)));
+		$result = $query->execute();
+		return $result !== 0;
+	}
+
+	public function accessToken(string $token): bool {
+		$query = $this->connection->getQueryBuilder();
+		$query->update(self::TABLE_TOKENS)
+			->set('accessed', $query->createNamedParameter(true, IQueryBuilder::PARAM_BOOL))
+			->set('timestamp', $query->createNamedParameter(time(), IQueryBuilder::PARAM_INT))
+			->where($query->expr()->eq('token', $query->createNamedParameter($token, IQueryBuilder::PARAM_STR)));
+		$result = $query->execute();
+		return $result !== 0;
+	}
+
+	public function invokeTokenScope($userId): void {
+		\OC_User::setIncognitoMode(true);
+		\OC_User::setUserId($userId);
+	}
+
+	public function createToken($editorId, File $file, IShare $share = null): string {
+		$token = $this->random->generate(64, ISecureRandom::CHAR_HUMAN_READABLE);
+		$query = $this->connection->getQueryBuilder();
+		$query->insert(self::TABLE_TOKENS)
+			->values([
+				'token' => $query->createNamedParameter($token),
+				'editor_id' => $query->createNamedParameter($editorId),
+				'file_id' => $query->createNamedParameter($file->getId()),
+				'user_id' => $query->createNamedParameter($this->userId),
+				'share_id' => $query->createNamedParameter($share !== null ? $share->getId(): null),
+				'timestamp' => $query->createNamedParameter(time())
+			]);
+		$query->execute();
+		return $token;
+	}
+
+	public function getFileForToken($userId, $fileId) {
+		$userFolder = $this->rootFolder->getUserFolder($userId);
+		return $userFolder->getById($fileId)[0];
+	}
+
+}
diff --git a/lib/private/DirectEditing/Token.php b/lib/private/DirectEditing/Token.php
new file mode 100644
index 00000000000..946699900b7
--- /dev/null
+++ b/lib/private/DirectEditing/Token.php
@@ -0,0 +1,76 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\DirectEditing;
+
+
+use OCP\DirectEditing\IToken;
+use OCP\Files\File;
+
+class Token implements IToken {
+
+	/** @var Manager */
+	private $manager;
+	private $data;
+
+	public function __construct(Manager $manager, $data) {
+		$this->manager = $manager;
+		$this->data = $data;
+	}
+
+	public function extend(): void {
+		$this->manager->refreshToken($this->data['token']);
+	}
+
+	public function invalidate(): void {
+		$this->manager->invalidateToken($this->data['token']);
+	}
+
+	public function getFile(): File {
+		if ($this->data['share_id'] !== null) {
+			return $this->manager->getShareForToken($this->data['share_id']);
+		}
+		return $this->manager->getFileForToken($this->data['user_id'], $this->data['file_id']);
+	}
+
+	public function getToken(): string {
+		return $this->data['token'];
+	}
+
+	public function useTokenScope(): void {
+		$this->manager->invokeTokenScope($this->data['user_id']);
+	}
+
+	public function hasBeenAccessed(): bool {
+		return $this->data['accessed'] === '1';
+	}
+
+	public function getEditor(): string {
+		return $this->data['editor_id'];
+	}
+
+	public function getUser(): string {
+		return $this->data['user_id'];
+	}
+
+}
diff --git a/lib/private/Server.php b/lib/private/Server.php
index b4af17ba288..9fb197fcb18 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -199,6 +199,8 @@ class Server extends ServerContainer implements IServerContainer {
 		$this->registerAlias(\OCP\Contacts\IManager::class, \OC\ContactsManager::class);
 		$this->registerAlias('ContactsManager', \OCP\Contacts\IManager::class);
 
+		$this->registerAlias(\OCP\DirectEditing\IManager::class, \OC\DirectEditing\Manager::class);
+
 		$this->registerAlias(IActionFactory::class, ActionFactory::class);
 
 
diff --git a/lib/public/DirectEditing/ACreateEmpty.php b/lib/public/DirectEditing/ACreateEmpty.php
new file mode 100644
index 00000000000..79684e33b80
--- /dev/null
+++ b/lib/public/DirectEditing/ACreateEmpty.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\DirectEditing;
+
+
+use OCP\Files\File;
+
+/**
+ * @since 18.0.0
+ */
+abstract class ACreateEmpty {
+
+	/**
+	 * Unique id for the creator to filter templates
+	 *
+	 * e.g. document/spreadsheet/presentation
+	 *
+	 * @since 18.0.0
+	 * @return string
+	 */
+	abstract public function getId(): string;
+
+	/**
+	 * Descriptive name for the create action
+	 *
+	 * e.g Create a new document
+	 *
+	 * @since 18.0.0
+	 * @return string
+	 */
+	abstract public function getName(): string;
+
+	/**
+	 * Default file extension for the new file
+	 *
+	 * @since 18.0.0
+	 * @return string
+	 */
+	abstract public function getExtension(): string;
+
+	/**
+	 * Add content when creating empty files
+	 *
+	 * @since 18.0.0
+	 * @param File $file
+	 */
+	public function create(File $file, string $creatorId = null, string $templateId = null): void {
+
+	}
+}
diff --git a/lib/public/DirectEditing/ACreateFromTemplate.php b/lib/public/DirectEditing/ACreateFromTemplate.php
new file mode 100644
index 00000000000..a731e8be595
--- /dev/null
+++ b/lib/public/DirectEditing/ACreateFromTemplate.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\DirectEditing;
+
+/**
+ * @since 18.0.0
+ */
+abstract class ACreateFromTemplate extends ACreateEmpty {
+
+	/**
+	 * List of available templates for the create from template action
+	 *
+	 * @since 18.0.0
+	 * @return array
+	 */
+	abstract public function getTemplates(): array;
+
+}
diff --git a/lib/public/DirectEditing/ATemplate.php b/lib/public/DirectEditing/ATemplate.php
new file mode 100644
index 00000000000..734317eebef
--- /dev/null
+++ b/lib/public/DirectEditing/ATemplate.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\DirectEditing;
+
+use JsonSerializable;
+
+/**
+ * Class ATemplate
+ *
+ * @package OCP\DirectEditing
+ * @since 18.0.0
+ */
+abstract class ATemplate implements JsonSerializable {
+
+	/**
+	 * Return a unique id so the app can identify the template
+	 *
+	 * @since 18.0.0
+	 * @return string
+	 */
+	abstract public function getId(): string;
+
+	/**
+	 * Return a title that is displayed to the user
+	 *
+	 * @since 18.0.0
+	 * @return string
+	 */
+	abstract public function getTitle(): string;
+
+	/**
+	 * Return a link to the template preview image
+	 *
+	 * @since 18.0.0
+	 * @return string
+	 */
+	abstract public function getPreview(): string;
+
+	/**
+	 * @since 18.0.0
+	 * @return array|mixed
+	 */
+	public function jsonSerialize() {
+		return [
+			'id' => $this->getId(),
+			'title' => $this->getTitle(),
+			'preview' => $this->getPreview(),
+		];
+	}
+}
diff --git a/lib/public/DirectEditing/IEditor.php b/lib/public/DirectEditing/IEditor.php
new file mode 100644
index 00000000000..a4fc87f7e15
--- /dev/null
+++ b/lib/public/DirectEditing/IEditor.php
@@ -0,0 +1,99 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\DirectEditing;
+
+
+use OCP\AppFramework\Http\Response;
+
+/**
+ * @since 18.0.0
+ */
+interface IEditor {
+
+	/**
+	 * Return a unique identifier for the editor
+	 *
+	 * e.g. richdocuments
+	 *
+	 * @since 18.0.0
+	 * @return string
+	 */
+	public function getId(): string;
+
+	/**
+	 * Return a readable name for the editor
+	 *
+	 * e.g. Collabora Online
+	 *
+	 * @since 18.0.0
+	 * @return string
+	 */
+	public function getName(): string;
+
+	/**
+	 * A list of mimetypes that should open the editor by default
+	 *
+	 * @since 18.0.0
+	 * @return array
+	 */
+	public function getMimetypes(): array;
+
+	/**
+	 * A list of mimetypes that can be opened in the editor optionally
+	 *
+	 * @since 18.0.0
+	 * @return array
+	 */
+	public function getMimetypesOptional(): array;
+
+	/**
+	 * Return a list of file creation options to be presented to the user
+	 *
+	 * @since 18.0.0
+	 * @return array of ICreateFromTemplate|ICreateEmpty
+	 */
+	public function getCreators(): array;
+
+	/**
+	 * Return if the view is able to securely view a file without downloading it to the browser
+	 *
+	 * @since 18.0.0
+	 * @return bool
+	 */
+	public function isSecure(): bool;
+
+	/**
+	 * Return a template response for displaying the editor
+	 *
+	 * open can only be called once when the client requests the editor with a one-time-use token
+	 * For handling editing and later requests, editors need to impelement their own token handling and take care of invalidation
+	 *
+	 * This behavior is similar to the current direct editing implementation in collabora where we generate a one-time token and switch over to the regular wopi token for the actual editing/saving process
+	 *
+	 * @since 18.0.0
+	 * @return Response
+	 */
+	public function open(IToken $token): Response;
+}
diff --git a/lib/public/DirectEditing/IManager.php b/lib/public/DirectEditing/IManager.php
new file mode 100644
index 00000000000..07b9c5a1e4e
--- /dev/null
+++ b/lib/public/DirectEditing/IManager.php
@@ -0,0 +1,88 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\DirectEditing;
+
+use OCP\AppFramework\Http\Response;
+use OCP\Files\NotPermittedException;
+use RuntimeException;
+
+/**
+ * Interface IManager
+ *
+ * @package OCP\DirectEditing
+ * @since 18.0.0
+ */
+interface IManager {
+
+	/**
+	 * Register a new editor
+	 *
+	 * @since 18.0.0
+	 * @param IEditor $directEditor
+	 */
+	public function registerDirectEditor(IEditor $directEditor): void;
+
+	/**
+	 * Open the editing page for a provided token
+	 *
+	 * @since 18.0.0
+	 * @param string $token
+	 * @return Response
+	 */
+	public function edit(string $token): Response;
+
+	/**
+	 * Create a new token based on the file path and editor details
+	 *
+	 * @since 18.0.0
+	 * @param string $path
+	 * @param string $editorId
+	 * @param string $creatorId
+	 * @param null $templateId
+	 * @return string
+	 * @throws NotPermittedException
+	 * @throws RuntimeException
+	 */
+	public function create(string $path, string $editorId, string $creatorId, $templateId = null): string;
+
+	/**
+	 * Get the token details for a given token
+	 *
+	 * @since 18.0.0
+	 * @param string $token
+	 * @return IToken
+	 */
+	public function getToken(string $token): IToken;
+
+	/**
+	 * Cleanup expired tokens
+	 *
+	 * @since 18.0.0
+	 * @return int number of deleted tokens
+	 */
+	public function cleanup(): int;
+
+}
+
diff --git a/lib/public/DirectEditing/IToken.php b/lib/public/DirectEditing/IToken.php
new file mode 100644
index 00000000000..a730493d76e
--- /dev/null
+++ b/lib/public/DirectEditing/IToken.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\DirectEditing;
+
+
+use OCP\Files\File;
+
+/**
+ * @since 18.0.0
+ */
+interface IToken {
+
+	/**
+	 * Extend the token validity time
+	 *
+	 * @since 18.0.0
+	 */
+	public function extend(): void;
+
+	/**
+	 * Invalidate the token
+	 *
+	 * @since 18.0.0
+	 */
+	public function invalidate(): void;
+
+	/**
+	 * Check if the token has already been used
+	 *
+	 * @since 18.0.0
+	 * @return bool
+	 */
+	public function hasBeenAccessed(): bool;
+
+	/**
+	 * Change to the user scope of the token
+	 *
+	 * @since 18.0.0
+	 */
+	public function useTokenScope(): void;
+
+	/**
+	 * Get the file that is related to the token
+	 *
+	 * @since 18.0.0
+	 * @return File
+	 */
+	public function getFile(): File;
+
+	/**
+	 * @since 18.0.0
+	 * @return string
+	 */
+	public function getEditor(): string;
+
+}
diff --git a/lib/public/DirectEditing/RegisterDirectEditorEvent.php b/lib/public/DirectEditing/RegisterDirectEditorEvent.php
new file mode 100644
index 00000000000..801e9f8fb1b
--- /dev/null
+++ b/lib/public/DirectEditing/RegisterDirectEditorEvent.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\DirectEditing;
+
+use OCP\EventDispatcher\Event;
+
+/**
+ * @since 18.0.0
+ */
+class RegisterDirectEditorEvent extends Event {
+
+	/**
+	 * @var IManager
+	 */
+	private $manager;
+
+	/**
+	 * RegisterDirectEditorEvent constructor.
+	 *
+	 * @param IManager $manager
+	 * @since 18.0.0
+	 */
+	public function __construct(IManager $manager) {
+		parent::__construct();
+		$this->manager = $manager;
+	}
+
+	/**
+	 * @since 18.0.0
+	 * @param IEditor $editor
+	 */
+	public function register(IEditor $editor): void {
+		$this->manager->registerDirectEditor($editor);
+	}
+
+}
diff --git a/version.php b/version.php
index abbcca4d03b..d5452bb0ab2 100644
--- a/version.php
+++ b/version.php
@@ -29,7 +29,7 @@
 // between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
 // when updating major/minor version number.
 
-$OC_Version = array(18, 0, 0, 1);
+$OC_Version = array(18, 0, 0, 2);
 
 // The human readable string
 $OC_VersionString = '18.0.0 Alpha';
-- 
GitLab