Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/ONLYOFFICE/onlyoffice-nextcloud.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Linnik <sergey.linnik@onlyoffice.com>2022-10-03 17:18:40 +0300
committerGitHub <noreply@github.com>2022-10-03 17:18:40 +0300
commit0f484a7eb23775ee82c6a41ec7e3d2c91790c774 (patch)
tree34ee134750d446d569d9ff70e43dd71c10498c30
parente2714c268dd5d153724c6ad33195db9bf2a1c8b0 (diff)
parent878b623af63c18951a567c1a2eca5ee6033cf599 (diff)
Merge pull request #591 from ONLYOFFICE/feature/federated-lock
Federated lock
-rw-r--r--appinfo/routes.php1
-rw-r--r--controller/callbackcontroller.php10
-rw-r--r--controller/federationcontroller.php22
-rw-r--r--lib/Migration/Version070400Date20220929111111.php22
-rw-r--r--lib/fileutility.php42
-rw-r--r--lib/keymanager.php59
-rw-r--r--lib/remoteinstance.php272
7 files changed, 321 insertions, 107 deletions
diff --git a/appinfo/routes.php b/appinfo/routes.php
index e2ab78e..df5ac70 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -48,6 +48,7 @@ return [
"ocs" => [
["name" => "federation#key", "url" => "/api/v1/key", "verb" => "POST"],
["name" => "federation#keylock", "url" => "/api/v1/keylock", "verb" => "POST"],
+ ["name" => "federation#healthcheck", "url" => "/api/v1/healthcheck", "verb" => "GET"],
["name" => "editorapi#config", "url" => "/api/v1/config/{fileId}", "verb" => "GET"],
["name" => "sharingapi#get_shares", "url" => "/api/v1/shares/{fileId}", "verb" => "GET"],
["name" => "sharingapi#set_shares", "url" => "/api/v1/shares", "verb" => "PUT"]
diff --git a/controller/callbackcontroller.php b/controller/callbackcontroller.php
index a2fd341..9270794 100644
--- a/controller/callbackcontroller.php
+++ b/controller/callbackcontroller.php
@@ -39,7 +39,6 @@ use OCP\Lock\LockedException;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager;
-use OCA\Files_Sharing\External\Storage as SharingExternalStorage;
use OCA\Files_Versions\Versions\IVersionManager;
use OCA\Onlyoffice\AppConfig;
@@ -47,6 +46,7 @@ use OCA\Onlyoffice\Crypt;
use OCA\Onlyoffice\DocumentService;
use OCA\Onlyoffice\FileVersions;
use OCA\Onlyoffice\KeyManager;
+use OCA\Onlyoffice\RemoteInstance;
use OCA\Onlyoffice\TemplateManager;
/**
@@ -529,8 +529,8 @@ class CallbackController extends Controller {
$prevIsForcesave = KeyManager::wasForcesave($fileId);
- if ($file->getStorage()->instanceOfStorage(SharingExternalStorage::class)) {
- $isLock = KeyManager::lockFederatedKey($file, $isForcesave, null);
+ if (RemoteInstance::isRemoteFile($file)) {
+ $isLock = RemoteInstance::lockRemoteKey($file, $isForcesave, null);
if ($isForcesave && !$isLock) {
break;
}
@@ -543,9 +543,9 @@ class CallbackController extends Controller {
return $file->putContent($newData);
});
- if ($file->getStorage()->instanceOfStorage(SharingExternalStorage::class)) {
+ if (RemoteInstance::isRemoteFile($file)) {
if ($isForcesave) {
- KeyManager::lockFederatedKey($file, false, $isForcesave);
+ RemoteInstance::lockRemoteKey($file, false, $isForcesave);
}
} else {
KeyManager::lock($fileId, false);
diff --git a/controller/federationcontroller.php b/controller/federationcontroller.php
index 8d68daf..27a9f64 100644
--- a/controller/federationcontroller.php
+++ b/controller/federationcontroller.php
@@ -27,12 +27,11 @@ use OCP\IRequest;
use OCP\ISession;
use OCP\Share\IManager;
-use OCA\Files_Sharing\External\Storage as SharingExternalStorage;
-
use OCA\Onlyoffice\AppConfig;
use OCA\Onlyoffice\DocumentService;
use OCA\Onlyoffice\FileUtility;
use OCA\Onlyoffice\KeyManager;
+use OCA\Onlyoffice\RemoteInstance;
/**
* OCS handler
@@ -136,8 +135,8 @@ class FederationController extends OCSController {
$fileId = $file->getId();
- if ($file->getStorage()->instanceOfStorage(SharingExternalStorage::class)) {
- $isLock = KeyManager::lockFederatedKey($file, $lock, $fs);
+ if (RemoteInstance::isRemoteFile($file)) {
+ $isLock = RemoteInstance::lockRemoteKey($file, $lock, $fs);
if (!$isLock) {
return new DataResponse(["error" => "Failed request"]);
}
@@ -151,4 +150,19 @@ class FederationController extends OCSController {
$this->logger->debug("Federated request lock for " . $fileId, ["app" => $this->appName]);
return new DataResponse();
}
+
+ /**
+ * Health check instance
+ *
+ * @return DataResponse
+ *
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ * @PublicPage
+ */
+ public function healthcheck() {
+ $this->logger->debug("Federated healthcheck", ["app" => $this->appName]);
+
+ return new DataResponse(["alive" => true]);
+ }
}
diff --git a/lib/Migration/Version070400Date20220929111111.php b/lib/Migration/Version070400Date20220929111111.php
index 0256498..d42b3db 100644
--- a/lib/Migration/Version070400Date20220929111111.php
+++ b/lib/Migration/Version070400Date20220929111111.php
@@ -76,6 +76,28 @@ class Version070400Date20220929111111 extends SimpleMigrationStep {
$table->addUniqueIndex(['share_id'], 'onlyoffice_share_id_index');
}
+ if (!$schema->hasTable('onlyoffice_instance')) {
+ $table = $schema->createTable('onlyoffice_instance');
+ $table->addColumn('id', 'integer', [
+ 'autoincrement' => true,
+ 'notnull' => true,
+ ]);
+ $table->addColumn('remote', 'string', [
+ 'notnull' => true,
+ 'length' => 128,
+ ]);
+ $table->addColumn('expire', 'bigint', [
+ 'notnull' => true,
+ 'default' => 0,
+ ]);
+ $table->addColumn('status', 'integer', [
+ 'notnull' => true,
+ 'default' => 0,
+ ]);
+ $table->setPrimaryKey(['id']);
+ $table->addUniqueIndex(['remote'], 'onlyoffice_remote_index');
+ }
+
return $schema;
}
diff --git a/lib/fileutility.php b/lib/fileutility.php
index 2d2a65f..cc50a45 100644
--- a/lib/fileutility.php
+++ b/lib/fileutility.php
@@ -27,10 +27,9 @@ use OCP\ILogger;
use OCP\ISession;
use OCP\Share\IManager;
-use OCA\Files_Sharing\External\Storage as SharingExternalStorage;
-
use OCA\Onlyoffice\AppConfig;
use OCA\Onlyoffice\KeyManager;
+use OCA\Onlyoffice\RemoteInstance;
/**
* File utility
@@ -221,10 +220,10 @@ class FileUtility {
$fileId = $file->getId();
if ($origin
- && $file->getStorage()->instanceOfStorage(SharingExternalStorage::class)) {
+ && RemoteInstance::isRemoteFile($file)) {
try {
- $key = $this->getFederatedKey($file);
+ $key = RemoteInstance::getRemoteKey($file);
if (!empty($key)) {
return $key;
@@ -263,41 +262,6 @@ class FileUtility {
}
/**
- * Generate unique document identifier in federated share
- *
- * @param File $file - file
- *
- * @return string
- */
- private function getFederatedKey($file) {
- $remote = rtrim($file->getStorage()->getRemote(), "/") . "/";
- $shareToken = $file->getStorage()->getToken();
- $internalPath = $file->getInternalPath();
-
- $httpClientService = \OC::$server->getHTTPClientService();
- $client = $httpClientService->newClient();
- $response = $client->post($remote . "ocs/v2.php/apps/" . $this->appName . "/api/v1/key?format=json", [
- "timeout" => 5,
- "body" => [
- "shareToken" => $shareToken,
- "path" => $internalPath
- ]
- ]);
- $body = \json_decode($response->getBody(), true);
-
- $data = $body["ocs"]["data"];
- if (!empty($data["error"])) {
- $this->logger->error("Error federated key " . $data["error"], ["app" => $this->appName]);
- return null;
- }
-
- $key = $data["key"];
- $this->logger->debug("Federated key: $key", ["app" => $this->appName]);
-
- return $key;
- }
-
- /**
* Generate unique file version key
*
* @param OCA\Files_Versions\Versions\IVersion $version - file version
diff --git a/lib/keymanager.php b/lib/keymanager.php
index f86f949..bdde78c 100644
--- a/lib/keymanager.php
+++ b/lib/keymanager.php
@@ -19,8 +19,6 @@
namespace OCA\Onlyoffice;
-use OCP\Files\File;
-
/**
* Key manager
*
@@ -29,11 +27,6 @@ use OCP\Files\File;
class KeyManager {
/**
- * App name
- */
- private const App_Name = "onlyoffice";
-
- /**
* Table name
*/
private const TableName_Key = "onlyoffice_filekey";
@@ -153,56 +146,4 @@ class KeyManager {
return $fs === "1";
}
-
- /**
- * Change lock status in the federated share
- *
- * @param File $file - file
- * @param bool $lock - status
- * @param bool $fs - status
- *
- * @return bool
- */
- public static function lockFederatedKey($file, $lock, $fs) {
- $logger = \OC::$server->getLogger();
- $action = $lock ? "lock" : "unlock";
-
- $remote = rtrim($file->getStorage()->getRemote(), "/") . "/";
- $shareToken = $file->getStorage()->getToken();
- $internalPath = $file->getInternalPath();
-
- $httpClientService = \OC::$server->getHTTPClientService();
- $client = $httpClientService->newClient();
- $data = [
- "timeout" => 5,
- "body" => [
- "shareToken" => $shareToken,
- "path" => $internalPath,
- "lock" => $lock
- ]
- ];
- if (!empty($fs)) {
- $data["body"]["fs"] = $fs;
- }
-
- try {
- $response = $client->post($remote . "ocs/v2.php/apps/" . self::App_Name . "/api/v1/keylock?format=json", $data);
- $body = \json_decode($response->getBody(), true);
-
- $data = $body["ocs"]["data"];
-
- if (empty($data)) {
- $logger->debug("Federated request " . $action . " for " . $file->getFileInfo()->getId() . " is successful", ["app" => self::App_Name]);
- return true;
- }
-
- if (!empty($data["error"])) {
- $logger->error("Error " . $action . " federated key for " . $file->getFileInfo()->getId() . ": " . $data["error"], ["app" => self::App_Name]);
- return false;
- }
- } catch (\Exception $e) {
- $logger->logException($e, ["message" => "Failed to request federated " . $action . " for " . $file->getFileInfo()->getId(), "app" => self::App_Name]);
- return false;
- }
- }
}
diff --git a/lib/remoteinstance.php b/lib/remoteinstance.php
new file mode 100644
index 0000000..000de44
--- /dev/null
+++ b/lib/remoteinstance.php
@@ -0,0 +1,272 @@
+<?php
+/**
+ *
+ * (c) Copyright Ascensio System SIA 2021
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+namespace OCA\Onlyoffice;
+
+use OCP\Files\File;
+
+use OCA\Files_Sharing\External\Storage as SharingExternalStorage;
+
+/**
+ * Remote instance manager
+ *
+ * @package OCA\Onlyoffice
+ */
+class RemoteInstance {
+
+ /**
+ * App name
+ */
+ private const App_Name = "onlyoffice";
+
+ /**
+ * Table name
+ */
+ private const TableName_Key = "onlyoffice_instance";
+
+ /**
+ * Time to live of remote instance (12 hours)
+ */
+ private static $ttl = 60 * 60 * 12;
+
+ /**
+ * Health remote list
+ */
+ private static $healthRemote = [];
+
+ /**
+ * Get remote instance
+ *
+ * @param string $remote - remote instance
+ *
+ * @return array
+ */
+ private static function get($remote) {
+ $connection = \OC::$server->getDatabaseConnection();
+ $select = $connection->prepare("
+ SELECT remote, expire, status
+ FROM `*PREFIX*" . self::TableName_Key . "`
+ WHERE `remote` = ?
+ ");
+ $result = $select->execute([$remote]);
+
+ $dbremote = $result ? $select->fetch() : [];
+
+ return $dbremote;
+ }
+
+ /**
+ * Store remote instance
+ *
+ * @param string $remote - remote instance
+ * @param bool $status - remote status
+ *
+ * @return bool
+ */
+ private static function set($remote, $status) {
+ $connection = \OC::$server->getDatabaseConnection();
+ $insert = $connection->prepare("
+ INSERT INTO `*PREFIX*" . self::TableName_Key . "`
+ (`remote`, `status`, `expire`)
+ VALUES (?, ?, ?)
+ ");
+ return (bool)$insert->execute([$remote, $status === true ? 1 : 0, time()]);
+ }
+
+ /**
+ * Update remote instance
+ *
+ * @param string $remote - remote instance
+ * @param bool $status - remote status
+ *
+ * @return bool
+ */
+ private static function update($remote, $status) {
+ $connection = \OC::$server->getDatabaseConnection();
+ $update = $connection->prepare("
+ UPDATE `*PREFIX*" . self::TableName_Key . "`
+ SET status = ?, expire = ?
+ WHERE remote = ?
+ ");
+ return (bool)$update->execute([$status === true ? 1 : 0, time(), $remote]);
+ }
+
+ /**
+ * Health check remote instance
+ *
+ * @param string $remote - remote instance
+ *
+ * @return bool
+ */
+ public static function healthCheck($remote) {
+ $logger = \OC::$server->getLogger();
+ $remote = rtrim($remote, "/") . "/";
+
+ if (array_key_exists($remote, self::$healthRemote)) {
+ $logger->debug("Remote instance " . $remote . " from local cache", ["app" => self::App_Name]);
+ return self::$healthRemote[$remote];
+ }
+
+ $dbremote = self::get($remote);
+ if (!empty($dbremote) && $dbremote["expire"] + self::$ttl > time()) {
+ $logger->debug("Remote instance " . $remote . " from database status " . $dbremote["status"], ["app" => self::App_Name]);
+ self::$healthRemote[$remote] = $dbremote["status"];
+ return self::$healthRemote[$remote];
+ }
+
+ $httpClientService = \OC::$server->getHTTPClientService();
+ $client = $httpClientService->newClient();
+
+ $status = false;
+ try {
+ $response = $client->get($remote . "ocs/v2.php/apps/" . self::App_Name . "/api/v1/healthcheck?format=json");
+ $body = json_decode($response->getBody(), true);
+
+ $data = $body["ocs"]["data"];
+
+ if (isset($data["alive"])) {
+ $status = $data["alive"] === true;
+ }
+
+ } catch (\Exception $e) {
+ $logger->logException($e, ["message" => "Failed to request federated health check for" . $remote, "app" => self::App_Name]);
+ }
+
+ if (empty($dbremote)) {
+ self::set($remote, $status);
+ } else {
+ self::update($remote, $status);
+ }
+
+ $logger->debug("Remote instance " . $remote . " was stored to database status " . $dbremote["status"], ["app" => self::App_Name]);
+
+ self::$healthRemote[$remote] = $status;
+
+ return self::$healthRemote[$remote];
+ }
+
+ /**
+ * Generate unique document identifier in federated share
+ *
+ * @param File $file - file
+ *
+ * @return string
+ */
+ public static function getRemoteKey($file) {
+ $logger = \OC::$server->getLogger();
+
+ $remote = rtrim($file->getStorage()->getRemote(), "/") . "/";
+ $shareToken = $file->getStorage()->getToken();
+ $internalPath = $file->getInternalPath();
+
+ $httpClientService = \OC::$server->getHTTPClientService();
+ $client = $httpClientService->newClient();
+ $response = $client->post($remote . "ocs/v2.php/apps/" . self::App_Name . "/api/v1/key?format=json", [
+ "timeout" => 5,
+ "body" => [
+ "shareToken" => $shareToken,
+ "path" => $internalPath
+ ]
+ ]);
+ $body = \json_decode($response->getBody(), true);
+
+ $data = $body["ocs"]["data"];
+ if (!empty($data["error"])) {
+ $logger->error("Error federated key " . $data["error"], ["app" => self::App_Name]);
+ return null;
+ }
+
+ $key = $data["key"];
+ $logger->debug("Federated key: $key", ["app" => self::App_Name]);
+
+ return $key;
+ }
+
+ /**
+ * Change lock status in the federated share
+ *
+ * @param File $file - file
+ * @param bool $lock - status
+ * @param bool $fs - status
+ *
+ * @return bool
+ */
+ public static function lockRemoteKey($file, $lock, $fs) {
+ $logger = \OC::$server->getLogger();
+ $action = $lock ? "lock" : "unlock";
+
+ $remote = rtrim($file->getStorage()->getRemote(), "/") . "/";
+ $shareToken = $file->getStorage()->getToken();
+ $internalPath = $file->getInternalPath();
+
+ $httpClientService = \OC::$server->getHTTPClientService();
+ $client = $httpClientService->newClient();
+ $data = [
+ "timeout" => 5,
+ "body" => [
+ "shareToken" => $shareToken,
+ "path" => $internalPath,
+ "lock" => $lock
+ ]
+ ];
+ if (!empty($fs)) {
+ $data["body"]["fs"] = $fs;
+ }
+
+ try {
+ $response = $client->post($remote . "ocs/v2.php/apps/" . self::App_Name . "/api/v1/keylock?format=json", $data);
+ $body = \json_decode($response->getBody(), true);
+
+ $data = $body["ocs"]["data"];
+
+ if (empty($data)) {
+ $logger->debug("Federated request " . $action . " for " . $file->getFileInfo()->getId() . " is successful", ["app" => self::App_Name]);
+ return true;
+ }
+
+ if (!empty($data["error"])) {
+ $logger->error("Error " . $action . " federated key for " . $file->getFileInfo()->getId() . ": " . $data["error"], ["app" => self::App_Name]);
+ return false;
+ }
+ } catch (\Exception $e) {
+ $logger->logException($e, ["message" => "Failed to request federated " . $action . " for " . $file->getFileInfo()->getId(), "app" => self::App_Name]);
+ return false;
+ }
+ }
+
+ /**
+ * Check of federated capable
+ *
+ * @param File $file - file
+ *
+ * @return bool
+ */
+ public static function isRemoteFile($file) {
+ $storage = $file->getStorage();
+
+ $alive = false;
+ $isFederated = $storage->instanceOfStorage(SharingExternalStorage::class);
+ if (!$isFederated) {
+ return false;
+ }
+
+ $alive = RemoteInstance::healthCheck($storage->getRemote());
+ return $alive;
+ }
+} \ No newline at end of file