From fdfad7636edc8b6da6f2f4e3106132b56d842544 Mon Sep 17 00:00:00 2001 From: cdujeu Date: Fri, 26 Aug 2016 12:40:54 +0200 Subject: [PATCH] Protect request-options queries with a unique api key. --- .../Core/Http/Response/FileReaderResponse.php | 5 +- .../pydio/Core/Services/ApiKeysService.php | 52 +++++++++++++++++-- .../KeystoreAuthFrontend.php | 9 +--- core/src/plugins/core.mq/MqManager.php | 9 ++++ .../plugins/uploader.html/SimpleUpload.php | 14 +++++ 5 files changed, 75 insertions(+), 14 deletions(-) diff --git a/core/src/core/src/pydio/Core/Http/Response/FileReaderResponse.php b/core/src/core/src/pydio/Core/Http/Response/FileReaderResponse.php index 2cea6309ab..5c7751daca 100644 --- a/core/src/core/src/pydio/Core/Http/Response/FileReaderResponse.php +++ b/core/src/core/src/pydio/Core/Http/Response/FileReaderResponse.php @@ -24,6 +24,8 @@ use Pydio\Access\Core\Model\AJXP_Node; use Pydio\Access\Driver\StreamProvider\FS\FsAccessWrapper; use Pydio\Core\Controller\HTMLWriter; +use Pydio\Core\Services\ApiKeysService; +use Pydio\Core\Services\AuthService; use Pydio\Core\Services\ConfService; use Pydio\Core\Utils\ApplicationState; use Pydio\Core\Utils\FileHelper; @@ -440,7 +442,8 @@ protected function sendToAccelerator($accelConfiguration, $localPathOrNode, $ser } // Pydio Agent acceleration - We make sure that request was really proxied by Agent, by checking a specific header. - if($accelConfiguration === "pydio" && array_key_exists("HTTP_X_PYDIO_DOWNLOAD_SUPPORTED", $serverParams)) { + if($accelConfiguration === "pydio" && array_key_exists("HTTP_X_PYDIO_DOWNLOAD_SUPPORTED", $serverParams) + && ApiKeysService::requestHasValidHeadersForAdminTask($serverParams, "go-upload", AuthService::getLoggedUser()->getId())) { if ($localPathOrNode instanceof AJXP_Node) { $options = MetaStreamWrapper::getResolvedOptionsForNode($localPathOrNode); diff --git a/core/src/core/src/pydio/Core/Services/ApiKeysService.php b/core/src/core/src/pydio/Core/Services/ApiKeysService.php index 98c3d15701..780a7b2ff7 100644 --- a/core/src/core/src/pydio/Core/Services/ApiKeysService.php +++ b/core/src/core/src/pydio/Core/Services/ApiKeysService.php @@ -20,10 +20,13 @@ */ namespace Pydio\Core\Services; +use Psr\Http\Message\ServerRequestInterface; use Pydio\Conf\Sql\SqlConfDriver; +use Pydio\Core\Exception\AuthRequiredException; use Pydio\Core\Exception\PydioException; use Pydio\Core\Utils\Http\UserAgent; use Pydio\Core\Utils\Vars\StringHelper; +use Pydio\Log\Core\Logger; defined('AJXP_EXEC') or die('Access not allowed'); @@ -79,21 +82,21 @@ public static function generatePairForAuthfront($userId, $deviceId = "", $device /** * @param $userId - * @param $deviceId + * @param $adminTaskId * @param string $restrictToIP * @return array * @throws PydioException * @throws \Exception */ - public static function generatePairForAdminTasks($userId, $deviceId, $restrictToIP = ""){ + public static function generatePairForAdminTask($adminTaskId, $userId, $restrictToIP = ""){ $store = self::getStore(); $token = StringHelper::generateRandomString(); $private = StringHelper::generateRandomString(); $data = [ - "USER_ID" => $userId, - "PRIVATE" => $private, - "DEVICE_ID" => $deviceId + "USER_ID" => $userId, + "PRIVATE" => $private, + "ADMIN_TASK_ID" => $adminTaskId ]; if(!empty($restrictToIP)){ $data["RESTRICT_TO_IP"] = $restrictToIP; @@ -103,6 +106,45 @@ public static function generatePairForAdminTasks($userId, $deviceId, $restrictTo } + /** + * @param $adminTaskId + * @param $userId + * @return array|null + * @throws PydioException + */ + public static function findPairForAdminTask($adminTaskId, $userId){ + + $keys = self::getStore()->simpleStoreList("keystore", $cursor, "", "serial", '%"ADMIN_TASK_ID";s:' . strlen($adminTaskId) . ':"' . $adminTaskId . '"%'); + foreach($keys as $kId => $kData){ + if($kData["USER_ID"] === $userId){ + return ["t" => $kId, "p" => $kData["PRIVATE"]]; + } + } + return null; + + } + + /** + * @param $serverData + * @param $adminTaskId + * @param $userId + * @return bool + */ + public static function requestHasValidHeadersForAdminTask($serverData, $adminTaskId, $userId){ + if(!isSet($serverData['HTTP_X_PYDIO_ADMIN_AUTH'])){ + Logger::error(__CLASS__, __FUNCTION__,"Invalid tokens for admin task $adminTaskId"); + return false; + } + list($t, $p) = explode(":", trim($serverData['HTTP_X_PYDIO_ADMIN_AUTH'])); + $existingKey = self::findPairForAdminTask("go-upload", $userId); + if($existingKey === null || $existingKey['p'] !== $p || $existingKey['t'] !== $t){ + Logger::error(__CLASS__, __FUNCTION__, "Invalid tokens for admin task $adminTaskId"); + return false; + } + Logger::debug(__CLASS__, "Valid tokens for admin task $adminTaskId"); + return true; + } + /** * @param $token * @param string $checkPrivate diff --git a/core/src/plugins/authfront.keystore/KeystoreAuthFrontend.php b/core/src/plugins/authfront.keystore/KeystoreAuthFrontend.php index 8a59b61c9f..158c5dd7b9 100644 --- a/core/src/plugins/authfront.keystore/KeystoreAuthFrontend.php +++ b/core/src/plugins/authfront.keystore/KeystoreAuthFrontend.php @@ -46,12 +46,7 @@ */ class KeystoreAuthFrontend extends AbstractAuthFrontend { - - /** - * @var SqlConfDriver $storage - */ - var $storage; - + /** * @param $httpVars * @param $varName @@ -140,8 +135,6 @@ function authTokenActions(ServerRequestInterface $requestInterface, ResponseInte if (!$ctx->hasUser()) { return null; } - $this->storage = ConfService::getConfStorageImpl(); - if (!($this->storage instanceof \Pydio\Conf\Sql\SqlConfDriver)) return false; $u = $ctx->getUser(); $user = $u->getId(); diff --git a/core/src/plugins/core.mq/MqManager.php b/core/src/plugins/core.mq/MqManager.php index 00a034d9ba..50aedb2732 100644 --- a/core/src/plugins/core.mq/MqManager.php +++ b/core/src/plugins/core.mq/MqManager.php @@ -34,6 +34,7 @@ use Pydio\Core\Model\ContextInterface; use Pydio\Core\PluginFramework\PluginsService; use Pydio\Core\Serializer\UserXML; +use Pydio\Core\Services\ApiKeysService; use Pydio\Core\Services\AuthService; use Pydio\Core\Services\ConfService; use Pydio\Core\Services\RepositoryService; @@ -536,6 +537,13 @@ public function generateCaddyFile($params) { $secure = $params["UPLOAD_SECURE"]; $path = "/" . trim($params["UPLOAD_PATH"], "/"); + // WE SHOULD HAVE A CONTEXT AT THIS POINT, INSTEAD OF CALLING ::getLoggedUser() + $adminKey = ApiKeysService::findPairForAdminTask("go-upload", AuthService::getLoggedUser()->getId()); + if($adminKey === null){ + $adminKey = ApiKeysService::generatePairForAdminTask("go-upload", AuthService::getLoggedUser()->getId(), $host); + } + $adminKeyString = $adminKey["t"].":".$adminKey["p"]; + $key = "http" . ($secure ? "s" : "") . "://" . $host . ":" . $port; $hosts[$key] = array_merge( (array)$hosts[$key], @@ -551,6 +559,7 @@ public function generateCaddyFile($params) { "pydioauth " . $path => [$tokenURL . "&device=upload"], "pydiopre " . $path => [$authURL, "{\n" . "\t\theader X-File-Direct-Upload request-options\n" . + "\t\theader X-Pydio-Admin-Auth $adminKeyString\n" . "\t}" ], "pydioupload " . $path => [], diff --git a/core/src/plugins/uploader.html/SimpleUpload.php b/core/src/plugins/uploader.html/SimpleUpload.php index 5c7059c3ac..691213e205 100644 --- a/core/src/plugins/uploader.html/SimpleUpload.php +++ b/core/src/plugins/uploader.html/SimpleUpload.php @@ -23,7 +23,10 @@ use Pydio\Access\Core\Model\AJXP_Node; use Pydio\Access\Core\Model\UserSelection; use Pydio\Core\Controller\Controller; +use Pydio\Core\Exception\AuthRequiredException; use Pydio\Core\Exception\PydioException; +use Pydio\Core\Model\ContextInterface; +use Pydio\Core\Services\ApiKeysService; use Pydio\Core\Services\LocaleService; use Pydio\Core\Utils\Vars\InputFilter; use Pydio\Core\Http\Message\ExternalUploadedFile; @@ -126,6 +129,17 @@ public function preProcess(\Psr\Http\Message\ServerRequestInterface &$request, \ throw new PydioException("Unrecognized direct upload status ". $externalUploadStatus); } + if($externalUploadStatus === ExternalUploadedFile::STATUS_REQUEST_OPTIONS){ + + /** @var ContextInterface $ctx */ + $ctx = $request->getAttribute("ctx"); + $uId = $ctx->getUser()->getId(); + if(!ApiKeysService::requestHasValidHeadersForAdminTask($request->getServerParams(), "go-upload", $uId)){ + throw new AuthRequiredException(); + } + + } + $uploadedFile = new ExternalUploadedFile($externalUploadStatus, $fileSizeH, $fileNameH); } else {