aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorRoeland Jago Douma <rullzer@users.noreply.github.com>2019-11-28 08:49:57 +0100
committerGitHub <noreply@github.com>2019-11-28 08:49:57 +0100
commit4173d9d74993bfae8d9c7dce5b983cad1b133751 (patch)
treebc234c62e53fe95d7ae4ec0d90507526f1a7f2a8 /lib
parent62dc32019146bdc186e110bde23dd210633acbe7 (diff)
parentbde624b07423de4a6b9e3aaae6371cd4f886c2de (diff)
downloadnextcloud-server-4173d9d74993bfae8d9c7dce5b983cad1b133751.tar.gz
nextcloud-server-4173d9d74993bfae8d9c7dce5b983cad1b133751.zip
Merge pull request #17625 from nextcloud/enh/noid/direct-editing
Direct editing API to allow file editing using a one-time token
Diffstat (limited to 'lib')
-rw-r--r--lib/composer/composer/autoload_classmap.php10
-rw-r--r--lib/composer/composer/autoload_static.php10
-rw-r--r--lib/private/DirectEditing/Manager.php236
-rw-r--r--lib/private/DirectEditing/Token.php76
-rw-r--r--lib/private/Server.php2
-rw-r--r--lib/public/DirectEditing/ACreateEmpty.php79
-rw-r--r--lib/public/DirectEditing/ACreateFromTemplate.php39
-rw-r--r--lib/public/DirectEditing/ATemplate.php71
-rw-r--r--lib/public/DirectEditing/IEditor.php99
-rw-r--r--lib/public/DirectEditing/IManager.php88
-rw-r--r--lib/public/DirectEditing/IToken.php77
-rw-r--r--lib/public/DirectEditing/RegisterDirectEditorEvent.php57
12 files changed, 844 insertions, 0 deletions
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..26adad9e572
--- /dev/null
+++ b/lib/private/DirectEditing/Manager.php
@@ -0,0 +1,236 @@
+<?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\DirectEditing\RegisterDirectEditorEvent;
+use OCP\EventDispatcher\IEventDispatcher;
+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 = [];
+ $return['templates'] = $templates;
+ return $return;
+ }
+
+ 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..ab7f7fd3ae8
--- /dev/null
+++ b/lib/public/DirectEditing/ACreateEmpty.php
@@ -0,0 +1,79 @@
+<?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;
+
+ /**
+ * Mimetype of the resulting created file
+ *
+ * @since 18.0.0
+ * @return string
+ */
+ abstract public function getMimetype(): 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..89420a63743
--- /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 ATemplate[]
+ */
+ 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..a2bc0d74255
--- /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 string[]
+ */
+ public function getMimetypes(): array;
+
+ /**
+ * A list of mimetypes that can be opened in the editor optionally
+ *
+ * @since 18.0.0
+ * @return string[]
+ */
+ public function getMimetypesOptional(): array;
+
+ /**
+ * Return a list of file creation options to be presented to the user
+ *
+ * @since 18.0.0
+ * @return ACreateFromTemplate[]|ACreateEmpty[]
+ */
+ 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);
+ }
+
+}