-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
[stable25] Require token for local editing #34564
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
1d02efd
1db6d96
ec230f9
c48b15f
be00587
7da8714
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
Signed-off-by: Joas Schilling <[email protected]>
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| <?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); | ||
| $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 noticeCode 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; | ||
| } | ||
|
|
||
| if ($entity->getExpirationTime() <= $this->timeFactory->getTime()) { | ||
| $this->mapper->delete($entity); | ||
| return new DataResponse([], Http::STATUS_NOT_FOUND); | ||
| } | ||
|
|
||
| return new DataResponse([ | ||
| 'userId' => $this->userId, | ||
| 'pathHash' => $pathHash, | ||
| 'expirationTime' => $entity->getExpirationTime(), | ||
| 'token' => $entity->getToken(), | ||
| ]); | ||
| } | ||
|
|
||
| } | ||
| 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 noticeCode 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 noticeCode 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 noticeCode 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 noticeCode 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'); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| <?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\DoesNotExistException; | ||
| use OCP\AppFramework\Db\MultipleObjectsReturnedException; | ||
| use OCP\AppFramework\Db\QBMapper; | ||
| use OCP\DB\Exception; | ||
| use OCP\IDBConnection; | ||
|
|
||
| class OpenLocalEditorMapper extends QBMapper { | ||
| public function __construct(IDBConnection $db) { | ||
| parent::__construct($db, 'open_local_editor', OpenLocalEditor::class); | ||
| } | ||
|
|
||
| /** | ||
| * @throws DoesNotExistException | ||
| * @throws MultipleObjectsReturnedException | ||
| * @throws Exception | ||
| */ | ||
| public function verifyToken(string $userId, string $pathHash, string $token): OpenLocalEditor { | ||
Check noticeCode scanning / Psalm MoreSpecificReturnType
The declared return type 'OCA\Files\Db\OpenLocalEditor' for OCA\Files\Db\OpenLocalEditorMapper::verifyToken is more specific than the inferred return type 'OCP\AppFramework\Db\Entity'
|
||
| $qb = $this->db->getQueryBuilder(); | ||
|
|
||
| $qb->select('*') | ||
| ->from($this->getTableName()) | ||
| ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId))) | ||
| ->andWhere($qb->expr()->eq('path_hash', $qb->createNamedParameter($pathHash))) | ||
| ->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token))); | ||
|
|
||
| return $this->findEntity($qb); | ||
Check noticeCode scanning / Psalm LessSpecificReturnStatement
The type 'OCP\AppFramework\Db\Entity' is more general than the declared return type 'OCA\Files\Db\OpenLocalEditor' for OCA\Files\Db\OpenLocalEditorMapper::verifyToken
|
||
| } | ||
| } | ||
Check notice
Code scanning / Psalm
PossiblyNullArgument