Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/files/appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<job>OCA\Files\BackgroundJob\DeleteOrphanedItems</job>
<job>OCA\Files\BackgroundJob\CleanupFileLocks</job>
<job>OCA\Files\BackgroundJob\CleanupDirectEditingTokens</job>
<job>OCA\Files\BackgroundJob\DeleteExpiredOpenLocalEditor</job>
</background-jobs>

<commands>
Expand Down
14 changes: 14 additions & 0 deletions apps/files/appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
*/
namespace OCA\Files\AppInfo;

use OCA\Files\Controller\OpenLocalEditorController;

/** @var Application $application */
$application = \OC::$server->query(Application::class);
$application->registerRoutes(
Expand Down Expand Up @@ -169,6 +171,18 @@
'url' => '/api/v1/transferownership/{id}',
'verb' => 'DELETE',
],
[
/** @see OpenLocalEditorController::create() */
'name' => 'OpenLocalEditor#create',
'url' => '/api/v1/openlocaleditor',
'verb' => 'POST',
],
[
/** @see OpenLocalEditorController::validate() */
'name' => 'OpenLocalEditor#validate',
'url' => '/api/v1/openlocaleditor/{token}',
'verb' => 'POST',
],
],
]
);
Expand Down
5 changes: 5 additions & 0 deletions apps/files/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
'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\\DeleteExpiredOpenLocalEditor' => $baseDir . '/../lib/BackgroundJob/DeleteExpiredOpenLocalEditor.php',
'OCA\\Files\\BackgroundJob\\DeleteOrphanedItems' => $baseDir . '/../lib/BackgroundJob/DeleteOrphanedItems.php',
'OCA\\Files\\BackgroundJob\\ScanFiles' => $baseDir . '/../lib/BackgroundJob/ScanFiles.php',
'OCA\\Files\\BackgroundJob\\TransferOwnership' => $baseDir . '/../lib/BackgroundJob/TransferOwnership.php',
Expand All @@ -35,9 +36,12 @@
'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\\OpenLocalEditorController' => $baseDir . '/../lib/Controller/OpenLocalEditorController.php',
'OCA\\Files\\Controller\\TemplateController' => $baseDir . '/../lib/Controller/TemplateController.php',
'OCA\\Files\\Controller\\TransferOwnershipController' => $baseDir . '/../lib/Controller/TransferOwnershipController.php',
'OCA\\Files\\Controller\\ViewController' => $baseDir . '/../lib/Controller/ViewController.php',
'OCA\\Files\\Db\\OpenLocalEditor' => $baseDir . '/../lib/Db/OpenLocalEditor.php',
'OCA\\Files\\Db\\OpenLocalEditorMapper' => $baseDir . '/../lib/Db/OpenLocalEditorMapper.php',
'OCA\\Files\\Db\\TransferOwnership' => $baseDir . '/../lib/Db/TransferOwnership.php',
'OCA\\Files\\Db\\TransferOwnershipMapper' => $baseDir . '/../lib/Db/TransferOwnershipMapper.php',
'OCA\\Files\\DirectEditingCapabilities' => $baseDir . '/../lib/DirectEditingCapabilities.php',
Expand All @@ -48,6 +52,7 @@
'OCA\\Files\\Listener\\LegacyLoadAdditionalScriptsAdapter' => $baseDir . '/../lib/Listener/LegacyLoadAdditionalScriptsAdapter.php',
'OCA\\Files\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php',
'OCA\\Files\\Migration\\Version11301Date20191205150729' => $baseDir . '/../lib/Migration/Version11301Date20191205150729.php',
'OCA\\Files\\Migration\\Version12101Date20221011153334' => $baseDir . '/../lib/Migration/Version12101Date20221011153334.php',
'OCA\\Files\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php',
'OCA\\Files\\Search\\FilesSearchProvider' => $baseDir . '/../lib/Search/FilesSearchProvider.php',
'OCA\\Files\\Service\\DirectEditingService' => $baseDir . '/../lib/Service/DirectEditingService.php',
Expand Down
5 changes: 5 additions & 0 deletions apps/files/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class ComposerStaticInitFiles
'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\\DeleteExpiredOpenLocalEditor' => __DIR__ . '/..' . '/../lib/BackgroundJob/DeleteExpiredOpenLocalEditor.php',
'OCA\\Files\\BackgroundJob\\DeleteOrphanedItems' => __DIR__ . '/..' . '/../lib/BackgroundJob/DeleteOrphanedItems.php',
'OCA\\Files\\BackgroundJob\\ScanFiles' => __DIR__ . '/..' . '/../lib/BackgroundJob/ScanFiles.php',
'OCA\\Files\\BackgroundJob\\TransferOwnership' => __DIR__ . '/..' . '/../lib/BackgroundJob/TransferOwnership.php',
Expand All @@ -50,9 +51,12 @@ class ComposerStaticInitFiles
'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\\OpenLocalEditorController' => __DIR__ . '/..' . '/../lib/Controller/OpenLocalEditorController.php',
'OCA\\Files\\Controller\\TemplateController' => __DIR__ . '/..' . '/../lib/Controller/TemplateController.php',
'OCA\\Files\\Controller\\TransferOwnershipController' => __DIR__ . '/..' . '/../lib/Controller/TransferOwnershipController.php',
'OCA\\Files\\Controller\\ViewController' => __DIR__ . '/..' . '/../lib/Controller/ViewController.php',
'OCA\\Files\\Db\\OpenLocalEditor' => __DIR__ . '/..' . '/../lib/Db/OpenLocalEditor.php',
'OCA\\Files\\Db\\OpenLocalEditorMapper' => __DIR__ . '/..' . '/../lib/Db/OpenLocalEditorMapper.php',
'OCA\\Files\\Db\\TransferOwnership' => __DIR__ . '/..' . '/../lib/Db/TransferOwnership.php',
'OCA\\Files\\Db\\TransferOwnershipMapper' => __DIR__ . '/..' . '/../lib/Db/TransferOwnershipMapper.php',
'OCA\\Files\\DirectEditingCapabilities' => __DIR__ . '/..' . '/../lib/DirectEditingCapabilities.php',
Expand All @@ -63,6 +67,7 @@ class ComposerStaticInitFiles
'OCA\\Files\\Listener\\LegacyLoadAdditionalScriptsAdapter' => __DIR__ . '/..' . '/../lib/Listener/LegacyLoadAdditionalScriptsAdapter.php',
'OCA\\Files\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php',
'OCA\\Files\\Migration\\Version11301Date20191205150729' => __DIR__ . '/..' . '/../lib/Migration/Version11301Date20191205150729.php',
'OCA\\Files\\Migration\\Version12101Date20221011153334' => __DIR__ . '/..' . '/../lib/Migration/Version12101Date20221011153334.php',
'OCA\\Files\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php',
'OCA\\Files\\Search\\FilesSearchProvider' => __DIR__ . '/..' . '/../lib/Search/FilesSearchProvider.php',
'OCA\\Files\\Service\\DirectEditingService' => __DIR__ . '/..' . '/../lib/Service/DirectEditingService.php',
Expand Down
21 changes: 16 additions & 5 deletions apps/files/js/filelist.js
Original file line number Diff line number Diff line change
Expand Up @@ -2808,12 +2808,23 @@
},

openLocalClient: function(path) {
var scheme = 'nc://';
var command = 'open';
var uid = OC.getCurrentUser().uid;
var url = scheme + command + '/' + uid + '@' + window.location.host + OC.encodePath(path);
var link = OC.linkToOCS('apps/files/api/v1', 2) + 'openlocaleditor?format=json';

window.location.href = url;
$.post(link, {
path
})
.success(function(result) {
var scheme = 'nc://';
var command = 'open';
var uid = OC.getCurrentUser().uid;
var url = scheme + command + '/' + uid + '@' + window.location.host + OC.encodePath(path);
url += '?token=' + result.ocs.data.token;

window.location.href = url;
})
.fail(function() {
OC.Notification.show(t('files', 'Failed to redirect to client'))
})
},

/**
Expand Down
61 changes: 61 additions & 0 deletions apps/files/lib/BackgroundJob/DeleteExpiredOpenLocalEditor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2022 Joas Schilling <[email protected]>
*
* @author Joas Schilling <[email protected]>
*
* @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\BackgroundJob;

use OCA\Files\Controller\OpenLocalEditorController;
use OCA\Files\Db\OpenLocalEditorMapper;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJob;
use OCP\BackgroundJob\TimedJob;

/**
* Delete all expired "Open local editor" token
*/
class DeleteExpiredOpenLocalEditor extends TimedJob {
protected OpenLocalEditorMapper $mapper;

public function __construct(
ITimeFactory $time,
OpenLocalEditorMapper $mapper
) {
parent::__construct($time);
$this->mapper = $mapper;

// Run every 12h
$this->interval = 12 * 3600;
$this->setTimeSensitivity(IJob::TIME_INSENSITIVE);
}

/**
* Makes the background job do its work
*
* @param array $argument unused argument
*/
public function run($argument): void {
$this->mapper->deleteExpiredTokens($this->time->getTime());
}
}
138 changes: 138 additions & 0 deletions apps/files/lib/Controller/OpenLocalEditorController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2022 Joas Schilling <[email protected]>
*
* @author Joas Schilling <[email protected]>
*
* @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 OCA\Files\Db\OpenLocalEditor;
use OCA\Files\Db\OpenLocalEditorMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\DB\Exception;
use OCP\IRequest;
use OCP\Security\ISecureRandom;
use Psr\Log\LoggerInterface;

class OpenLocalEditorController extends OCSController {
public const TOKEN_LENGTH = 128;
public const TOKEN_DURATION = 600; // 10 Minutes
public const TOKEN_RETRIES = 50;

protected ITimeFactory $timeFactory;
protected OpenLocalEditorMapper $mapper;
protected ISecureRandom $secureRandom;
protected LoggerInterface $logger;
protected ?string $userId;

public function __construct(
string $appName,
IRequest $request,
ITimeFactory $timeFactory,
OpenLocalEditorMapper $mapper,
ISecureRandom $secureRandom,
LoggerInterface $logger,
?string $userId
) {
parent::__construct($appName, $request);

$this->timeFactory = $timeFactory;
$this->mapper = $mapper;
$this->secureRandom = $secureRandom;
$this->logger = $logger;
$this->userId = $userId;
}

/**
* @NoAdminRequired
* @UserRateThrottle(limit=10, period=120)
*/
public function create(string $path): DataResponse {
$pathHash = sha1($path);

$entity = new OpenLocalEditor();
$entity->setUserId($this->userId);

Check notice

Code scanning / Psalm

PossiblyNullArgument

Argument 1 of setUserId cannot be null, possibly null value provided
$entity->setPathHash($pathHash);
$entity->setExpirationTime($this->timeFactory->getTime() + self::TOKEN_DURATION); // Expire in 10 minutes

for ($i = 1; $i <= self::TOKEN_RETRIES; $i++) {
$token = $this->secureRandom->generate(self::TOKEN_LENGTH, ISecureRandom::CHAR_ALPHANUMERIC);
$entity->setToken($token);

try {
$this->mapper->insert($entity);

return new DataResponse([
'userId' => $this->userId,
'pathHash' => $pathHash,
'expirationTime' => $entity->getExpirationTime(),
'token' => $entity->getToken(),
]);
} catch (Exception $e) {
if ($e->getCode() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
// Only retry on unique constraint violation
throw $e;
}
}
}

$this->logger->error('Giving up after ' . self::TOKEN_RETRIES . ' retries to generate a unique local editor token for path hash: ' . $pathHash);
return new DataResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
}

/**
* @NoAdminRequired
* @BruteForceProtection(action=openLocalEditor)
*/
public function validate(string $path, string $token): DataResponse {
$pathHash = sha1($path);

try {
$entity = $this->mapper->verifyToken($this->userId, $pathHash, $token);

Check notice

Code scanning / Psalm

PossiblyNullArgument

Argument 1 of OCA\Files\Db\OpenLocalEditorMapper::verifyToken cannot be null, possibly null value provided
} catch (DoesNotExistException $e) {
$response = new DataResponse([], Http::STATUS_NOT_FOUND);
$response->throttle(['userId' => $this->userId, 'pathHash' => $pathHash]);
return $response;
}

$this->mapper->delete($entity);

if ($entity->getExpirationTime() <= $this->timeFactory->getTime()) {
$response = new DataResponse([], Http::STATUS_NOT_FOUND);
$response->throttle(['userId' => $this->userId, 'pathHash' => $pathHash]);
return $response;
}

return new DataResponse([
'userId' => $this->userId,
'pathHash' => $pathHash,
'expirationTime' => $entity->getExpirationTime(),
'token' => $entity->getToken(),
]);
}

}
60 changes: 60 additions & 0 deletions apps/files/lib/Db/OpenLocalEditor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2022 Joas Schilling <[email protected]>
*
* @author Joas Schilling <[email protected]>
*
* @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\Db;

use OCP\AppFramework\Db\Entity;

/**
* @method void setUserId(string $userId)
* @method string getUserId()
* @method void setPathHash(string $pathHash)
* @method string getPathHash()
* @method void setExpirationTime(int $expirationTime)
* @method int getExpirationTime()
* @method void setToken(string $token)
* @method string getToken()
*/
class OpenLocalEditor extends Entity {
/** @var string */
protected $userId;

Check notice

Code scanning / Psalm

PropertyNotSetInConstructor

Property OCA\Files\Db\OpenLocalEditor::$userId is not defined in constructor of OCA\Files\Db\OpenLocalEditor or in any methods called in the constructor

/** @var string */
protected $pathHash;

Check notice

Code scanning / Psalm

PropertyNotSetInConstructor

Property OCA\Files\Db\OpenLocalEditor::$pathHash is not defined in constructor of OCA\Files\Db\OpenLocalEditor or in any methods called in the constructor

/** @var int */
protected $expirationTime;

Check notice

Code scanning / Psalm

PropertyNotSetInConstructor

Property OCA\Files\Db\OpenLocalEditor::$expirationTime is not defined in constructor of OCA\Files\Db\OpenLocalEditor or in any methods called in the constructor

/** @var string */
protected $token;

Check notice

Code scanning / Psalm

PropertyNotSetInConstructor

Property OCA\Files\Db\OpenLocalEditor::$token is not defined in constructor of OCA\Files\Db\OpenLocalEditor or in any methods called in the constructor

public function __construct() {
$this->addType('userId', 'string');
$this->addType('pathHash', 'string');
$this->addType('expirationTime', 'integer');
$this->addType('token', 'string');
}
}
Loading