From 12aaa942acc5d480bd3d8a7fedcacb69443dabf2 Mon Sep 17 00:00:00 2001 From: Mukunda Modell Date: Thu, 7 Aug 2014 18:56:19 -0700 Subject: [PATCH] Add a CanCDN flag to uploaded files Summary: CanCDN flag indicates that a file can be served + cached via anonymous content distribution networks. Once D10054 lands, any files that lack the CanCDN flag will require a one-time-use token and headers will prohibit cache to protect sensitive files from unauthorized access. This diff separates the CanCDN changes from the code that enforces these restrictions in D10054 so that the changes can be tested and refined independently. Test Plan: Work in progress Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: rush898, qgil, epriestley, aklapper, Korvin Maniphest Tasks: T5685 Differential Revision: https://secure.phabricator.com/D10166 --- scripts/util/add_macro.php | 1 + .../auth/provider/PhabricatorAuthProvider.php | 1 + .../files/PhabricatorImageTransformer.php | 5 ++ .../PhabricatorFileComposeController.php | 1 + .../files/storage/PhabricatorFile.php | 64 ++++++++++++++++++- .../PhabricatorMacroEditController.php | 2 + ...bricatorPeopleProfilePictureController.php | 1 + ...habricatorProjectEditPictureController.php | 1 + 8 files changed, 75 insertions(+), 1 deletion(-) diff --git a/scripts/util/add_macro.php b/scripts/util/add_macro.php index e2b9ea5608..cbfe8480f6 100755 --- a/scripts/util/add_macro.php +++ b/scripts/util/add_macro.php @@ -52,6 +52,7 @@ $file = PhabricatorFile::newFromFileData( $data, array( 'name' => basename($path), + 'canCDN' => true, )); $macro = id(new PhabricatorFileImageMacro()) diff --git a/src/applications/auth/provider/PhabricatorAuthProvider.php b/src/applications/auth/provider/PhabricatorAuthProvider.php index 0c7948ed1b..9daae24df1 100644 --- a/src/applications/auth/provider/PhabricatorAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorAuthProvider.php @@ -243,6 +243,7 @@ abstract class PhabricatorAuthProvider { $image_uri, array( 'name' => $name, + 'canCDN' => true )); unset($unguarded); diff --git a/src/applications/files/PhabricatorImageTransformer.php b/src/applications/files/PhabricatorImageTransformer.php index 586583c2b3..e13494dd67 100644 --- a/src/applications/files/PhabricatorImageTransformer.php +++ b/src/applications/files/PhabricatorImageTransformer.php @@ -16,6 +16,7 @@ final class PhabricatorImageTransformer { array( 'name' => 'meme-'.$file->getName(), 'ttl' => time() + 60 * 60 * 24, + 'canCDN' => true, )); } @@ -30,6 +31,7 @@ final class PhabricatorImageTransformer { $image, array( 'name' => 'thumb-'.$file->getName(), + 'canCDN' => true, )); } @@ -45,6 +47,7 @@ final class PhabricatorImageTransformer { $image, array( 'name' => 'profile-'.$file->getName(), + 'canCDN' => true, )); } @@ -58,6 +61,7 @@ final class PhabricatorImageTransformer { $image, array( 'name' => 'preview-'.$file->getName(), + 'canCDN' => true, )); } @@ -79,6 +83,7 @@ final class PhabricatorImageTransformer { $image, array( 'name' => 'conpherence-'.$file->getName(), + 'canCDN' => true, )); } diff --git a/src/applications/files/controller/PhabricatorFileComposeController.php b/src/applications/files/controller/PhabricatorFileComposeController.php index ec27ea836a..d698509e96 100644 --- a/src/applications/files/controller/PhabricatorFileComposeController.php +++ b/src/applications/files/controller/PhabricatorFileComposeController.php @@ -38,6 +38,7 @@ final class PhabricatorFileComposeController $data, array( 'name' => 'project.png', + 'canCDN' => true, )); $content = array( diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 5e45d8f62c..5ba04f8838 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -7,10 +7,12 @@ final class PhabricatorFile extends PhabricatorFileDAO PhabricatorFlaggableInterface, PhabricatorPolicyInterface { + const ONETIME_TEMPORARY_TOKEN_TYPE = 'file:onetime'; const STORAGE_FORMAT_RAW = 'raw'; const METADATA_IMAGE_WIDTH = 'width'; const METADATA_IMAGE_HEIGHT = 'height'; + const METADATA_CAN_CDN = 'cancdn'; protected $name; protected $mimeType; @@ -202,7 +204,6 @@ final class PhabricatorFile extends PhabricatorFileDAO } private static function buildFromFileData($data, array $params = array()) { - $selector = PhabricatorEnv::newObjectFromConfig('storage.engine-selector'); if (isset($params['storageEngines'])) { $engines = $params['storageEngines']; @@ -270,6 +271,10 @@ final class PhabricatorFile extends PhabricatorFileDAO $file->setViewPolicy($params['viewPolicy']); } + if (idx($params, 'canCDN')) { + $file->setCanCDN(true); + } + $file->setStorageEngine($engine_identifier); $file->setStorageHandle($data_handle); @@ -852,6 +857,63 @@ final class PhabricatorFile extends PhabricatorFileDAO return idx($this->metadata, self::METADATA_IMAGE_WIDTH); } + public function getCanCDN() { + if (!$this->isViewableImage()) { + return false; + } + return idx($this->metadata, self::METADATA_CAN_CDN); + } + + public function setCanCDN($can_cdn) { + $this->metadata[self::METADATA_CAN_CDN] = $can_cdn ? 1 : 0; + return $this; + } + + protected function generateOneTimeToken() { + $key = Filesystem::readRandomCharacters(16); + + // Save the new secret. + return id(new PhabricatorAuthTemporaryToken()) + ->setObjectPHID($this->getPHID()) + ->setTokenType(self::ONETIME_TEMPORARY_TOKEN_TYPE) + ->setTokenExpires(time() + phutil_units('1 hour in seconds')) + ->setTokenCode(PhabricatorHash::digest($key)) + ->save(); + } + + public function validateOneTimeToken($token_code) { + $token = id(new PhabricatorAuthTemporaryTokenQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withObjectPHIDs(array($this->getPHID())) + ->withTokenTypes(array(self::ONETIME_TEMPORARY_TOKEN_TYPE)) + ->withExpired(false) + ->withTokenCodes(array($token_code)) + ->executeOne(); + + return $token; + } + + /** Get the CDN uri for this file + * This will generate a one-time-use token if + * security.alternate_file_domain is set in the config. + */ + public function getCDNURIWithToken() { + if (!$this->getPHID()) { + throw new Exception( + 'You must save a file before you can generate a CDN URI.'); + } + $name = phutil_escape_uri($this->getName()); + + $path = '/file/data' + .'/'.$this->getSecretKey() + .'/'.$this->getPHID() + .'/'.$this->generateOneTimeToken() + .'/'.$name; + return PhabricatorEnv::getCDNURI($path); + } + + + /** * Write the policy edge between this file and some object. * diff --git a/src/applications/macro/controller/PhabricatorMacroEditController.php b/src/applications/macro/controller/PhabricatorMacroEditController.php index 79393c7536..5442754e42 100644 --- a/src/applications/macro/controller/PhabricatorMacroEditController.php +++ b/src/applications/macro/controller/PhabricatorMacroEditController.php @@ -64,6 +64,7 @@ final class PhabricatorMacroEditController extends PhabricatorMacroController { 'name' => $request->getStr('name'), 'authorPHID' => $user->getPHID(), 'isExplicitUpload' => true, + 'canCDN' => true, )); } else if ($request->getStr('url')) { try { @@ -73,6 +74,7 @@ final class PhabricatorMacroEditController extends PhabricatorMacroController { 'name' => $request->getStr('name'), 'authorPHID' => $user->getPHID(), 'isExplicitUpload' => true, + 'canCDN' => true, )); } catch (Exception $ex) { $errors[] = pht('Could not fetch URL: %s', $ex->getMessage()); diff --git a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php index afee9ea84f..55cfb8731c 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php @@ -53,6 +53,7 @@ final class PhabricatorPeopleProfilePictureController $_FILES['picture'], array( 'authorPHID' => $viewer->getPHID(), + 'canCDN' => true, )); } else { $e_file = pht('Required'); diff --git a/src/applications/project/controller/PhabricatorProjectEditPictureController.php b/src/applications/project/controller/PhabricatorProjectEditPictureController.php index 6170d6dfa0..98282407be 100644 --- a/src/applications/project/controller/PhabricatorProjectEditPictureController.php +++ b/src/applications/project/controller/PhabricatorProjectEditPictureController.php @@ -50,6 +50,7 @@ final class PhabricatorProjectEditPictureController $_FILES['picture'], array( 'authorPHID' => $viewer->getPHID(), + 'canCDN' => true, )); } else { $e_file = pht('Required');