@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Protect file data with a one-time-token

Test Plan: currently untested work in progress

Reviewers: #blessed_reviewers, epriestley

Subscribers: rush898, aklapper, Korvin, epriestley

Projects: #wikimedia

Maniphest Tasks: T5685

Differential Revision: https://secure.phabricator.com/D10054

authored by

Mukunda Modell and committed by
epriestley
25ae4c45 c0919be0

+127 -25
+20
resources/sql/autopatches/20140731.cancdn.php
··· 1 + <?php 2 + 3 + $table = new PhabricatorFile(); 4 + $conn_w = $table->establishConnection('w'); 5 + foreach (new LiskMigrationIterator($table) as $file) { 6 + $id = $file->getID(); 7 + echo "Updating flags for file {$id}...\n"; 8 + $meta = $file->getMetadata(); 9 + if (!idx($meta, 'canCDN')) { 10 + 11 + $meta['canCDN'] = true; 12 + 13 + queryfx( 14 + $conn_w, 15 + 'UPDATE %T SET metadata = %s WHERE id = %d', 16 + $table->getTableName(), 17 + json_encode($meta), 18 + $id); 19 + } 20 + }
+2
src/applications/files/application/PhabricatorFilesApplication.php
··· 52 52 'delete/(?P<id>[1-9]\d*)/' => 'PhabricatorFileDeleteController', 53 53 'edit/(?P<id>[1-9]\d*)/' => 'PhabricatorFileEditController', 54 54 'info/(?P<phid>[^/]+)/' => 'PhabricatorFileInfoController', 55 + 'data/(?P<key>[^/]+)/(?P<phid>[^/]+)/(?P<token>[^/]+)/.*' 56 + => 'PhabricatorFileDataController', 55 57 'data/(?P<key>[^/]+)/(?P<phid>[^/]+)/.*' 56 58 => 'PhabricatorFileDataController', 57 59 'proxy/' => 'PhabricatorFileProxyController',
+94 -18
src/applications/files/controller/PhabricatorFileDataController.php
··· 4 4 5 5 private $phid; 6 6 private $key; 7 + private $token; 7 8 8 9 public function willProcessRequest(array $data) { 9 10 $this->phid = $data['phid']; 10 11 $this->key = $data['key']; 12 + $this->token = idx($data, 'token'); 11 13 } 12 14 13 15 public function shouldRequireLogin() { 14 16 return false; 17 + } 18 + 19 + protected function checkFileAndToken($file) { 20 + if (!$file) { 21 + return new Aphront404Response(); 22 + } 23 + 24 + if (!$file->validateSecretKey($this->key)) { 25 + return new Aphront403Response(); 26 + } 27 + 28 + return null; 15 29 } 16 30 17 31 public function processRequest() { 18 32 $request = $this->getRequest(); 19 33 20 34 $alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain'); 21 - $uri = new PhutilURI($alt); 22 - $alt_domain = $uri->getDomain(); 23 - if ($alt_domain && ($alt_domain != $request->getHost())) { 35 + $base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); 36 + $alt_uri = new PhutilURI($alt); 37 + $alt_domain = $alt_uri->getDomain(); 38 + $req_domain = $request->getHost(); 39 + $main_domain = id(new PhutilURI($base_uri))->getDomain(); 40 + 41 + $cache_response = true; 42 + 43 + if (empty($alt) || $main_domain == $alt_domain) { 44 + // Alternate files domain isn't configured or it's set 45 + // to the same as the default domain 46 + 47 + // load the file with permissions checks; 48 + $file = id(new PhabricatorFileQuery()) 49 + ->setViewer($request->getUser()) 50 + ->withPHIDs(array($this->phid)) 51 + ->executeOne(); 52 + 53 + $error_response = $this->checkFileAndToken($file); 54 + if ($error_response) { 55 + return $error_response; 56 + } 57 + 58 + // when the file is not CDNable, don't allow cache 59 + $cache_response = $file->getCanCDN(); 60 + } else if ($req_domain != $alt_domain) { 61 + // Alternate domain is configured but this request isn't using it 62 + 63 + // load the file with permissions checks; 64 + $file = id(new PhabricatorFileQuery()) 65 + ->setViewer($request->getUser()) 66 + ->withPHIDs(array($this->phid)) 67 + ->executeOne(); 68 + 69 + $error_response = $this->checkFileAndToken($file); 70 + if ($error_response) { 71 + return $error_response; 72 + } 73 + 74 + // if the user can see the file, generate a token; 75 + // redirect to the alt domain with the token; 24 76 return id(new AphrontRedirectResponse()) 25 - ->setURI($uri->setPath($request->getPath())); 26 - } 77 + ->setURI($file->getCDNURIWithToken()); 27 78 28 - // NOTE: This endpoint will ideally be accessed via CDN or otherwise on 29 - // a non-credentialed domain. Knowing the file's secret key gives you 30 - // access, regardless of authentication on the request itself. 79 + } else { 80 + // We are using the alternate domain 31 81 32 - $file = id(new PhabricatorFileQuery()) 33 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 34 - ->withPHIDs(array($this->phid)) 35 - ->executeOne(); 36 - if (!$file) { 37 - return new Aphront404Response(); 38 - } 82 + // load the file, bypassing permission checks; 83 + $file = id(new PhabricatorFileQuery()) 84 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 85 + ->withPHIDs(array($this->phid)) 86 + ->executeOne(); 39 87 40 - if (!$file->validateSecretKey($this->key)) { 41 - return new Aphront403Response(); 88 + $error_response = $this->checkFileAndToken($file); 89 + if ($error_response) { 90 + return $error_response; 91 + } 92 + 93 + if ($this->token) { 94 + // validate the token, if it is valid, continue 95 + $validated_token = $file->validateOneTimeToken($this->token); 96 + 97 + if (!$validated_token) { 98 + return new Aphront403Response(); 99 + } 100 + // return the file data without cache headers 101 + $cache_response = false; 102 + } else if (!$file->getCanCDN()) { 103 + // file cannot be served via cdn, and no token given 104 + // redirect to the main domain to aquire a token 105 + $file_uri = id(new PhutilURI($file->getViewURI())) 106 + ->setDomain($main_domain); 107 + 108 + return id(new AphrontRedirectResponse()) 109 + ->setURI($file_uri); 110 + } 42 111 } 43 112 44 113 $data = $file->loadFileData(); 45 114 $response = new AphrontFileResponse(); 46 115 $response->setContent($data); 47 - $response->setCacheDurationInSeconds(60 * 60 * 24 * 30); 116 + if ($cache_response) { 117 + $response->setCacheDurationInSeconds(60 * 60 * 24 * 30); 118 + } 48 119 49 120 // NOTE: It's important to accept "Range" requests when playing audio. 50 121 // If we don't, Safari has difficulty figuring out how long sounds are ··· 58 129 $response->setHTTPResponseCode(206); 59 130 $response->setRange((int)$matches[1], (int)$matches[2]); 60 131 } 132 + } else if (isset($validated_token)) { 133 + // consume the one-time token if we have one. 134 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 135 + $validated_token->delete(); 136 + unset($unguarded); 61 137 } 62 138 63 139 $is_viewable = $file->isViewableInBrowser();
+11 -7
src/applications/files/storage/PhabricatorFile.php
··· 873 873 $key = Filesystem::readRandomCharacters(16); 874 874 875 875 // Save the new secret. 876 - return id(new PhabricatorAuthTemporaryToken()) 877 - ->setObjectPHID($this->getPHID()) 878 - ->setTokenType(self::ONETIME_TEMPORARY_TOKEN_TYPE) 879 - ->setTokenExpires(time() + phutil_units('1 hour in seconds')) 880 - ->setTokenCode(PhabricatorHash::digest($key)) 881 - ->save(); 876 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 877 + $token = id(new PhabricatorAuthTemporaryToken()) 878 + ->setObjectPHID($this->getPHID()) 879 + ->setTokenType(self::ONETIME_TEMPORARY_TOKEN_TYPE) 880 + ->setTokenExpires(time() + phutil_units('1 hour in seconds')) 881 + ->setTokenCode(PhabricatorHash::digest($key)) 882 + ->save(); 883 + unset($unguarded); 884 + 885 + return $key; 882 886 } 883 887 884 888 public function validateOneTimeToken($token_code) { ··· 887 891 ->withObjectPHIDs(array($this->getPHID())) 888 892 ->withTokenTypes(array(self::ONETIME_TEMPORARY_TOKEN_TYPE)) 889 893 ->withExpired(false) 890 - ->withTokenCodes(array($token_code)) 894 + ->withTokenCodes(array(PhabricatorHash::digest($token_code))) 891 895 ->executeOne(); 892 896 893 897 return $token;