Page MenuHomePhabricator

D10054.id24232.diff
No OneTemporary

D10054.id24232.diff

diff --git a/src/applications/files/application/PhabricatorFilesApplication.php b/src/applications/files/application/PhabricatorFilesApplication.php
--- a/src/applications/files/application/PhabricatorFilesApplication.php
+++ b/src/applications/files/application/PhabricatorFilesApplication.php
@@ -51,6 +51,8 @@
'comment/(?P<id>[1-9]\d*)/' => 'PhabricatorFileCommentController',
'delete/(?P<id>[1-9]\d*)/' => 'PhabricatorFileDeleteController',
'info/(?P<phid>[^/]+)/' => 'PhabricatorFileInfoController',
+ 'data/(?P<key>[^/]+)/(?P<phid>[^/]+)/(?P<token>[^/]+)/.*'
+ => 'PhabricatorFileDataController',
'data/(?P<key>[^/]+)/(?P<phid>[^/]+)/.*'
=> 'PhabricatorFileDataController',
'proxy/' => 'PhabricatorFileProxyController',
diff --git a/src/applications/files/controller/PhabricatorFileDataController.php b/src/applications/files/controller/PhabricatorFileDataController.php
--- a/src/applications/files/controller/PhabricatorFileDataController.php
+++ b/src/applications/files/controller/PhabricatorFileDataController.php
@@ -4,10 +4,12 @@
private $phid;
private $key;
+ private $token;
public function willProcessRequest(array $data) {
$this->phid = $data['phid'];
$this->key = $data['key'];
+ $this->token = idx($data,'token');
}
public function shouldRequireLogin() {
@@ -17,22 +19,68 @@
public function processRequest() {
$request = $this->getRequest();
- $alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain');
- $uri = new PhutilURI($alt);
- $alt_domain = $uri->getDomain();
- if ($alt_domain && ($alt_domain != $request->getHost())) {
- return id(new AphrontRedirectResponse())
- ->setURI($uri->setPath($request->getPath()));
- }
-
// NOTE: This endpoint will ideally be accessed via CDN or otherwise on
// a non-credentialed domain. Knowing the file's secret key gives you
// access, regardless of authentication on the request itself.
- $file = id(new PhabricatorFileQuery())
- ->setViewer(PhabricatorUser::getOmnipotentUser())
- ->withPHIDs(array($this->phid))
- ->executeOne();
+ $alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain');
+ $alt_uri = new PhutilURI($alt);
+ $alt_domain = $alt_uri->getDomain();
+ $req_domain = id(new PhutilURI(PhabricatorEnv::getRequestBaseURI()))
+ ->getDomain();
+
+
+ $cache_response = true;
+
+ if (empty($alt)) {
+ // load the file with permissions checks;
+ $file = id(new PhabricatorFileQuery())
+ ->setViewer($request->getUser())
+ ->withPHIDs(array($this->phid))
+ ->executeOne();
+ //return the file data without cache headers;
+ $cache_response = false;
+ } else if ($req_domain != $alt_domain) {
+ //load the file with permissions checks;
+ $file = id(new PhabricatorFileQuery())
+ ->setViewer($request->getUser())
+ ->withPHIDs(array($this->phid))
+ ->executeOne();
+
+ if (!$file) {
+ return new Aphront404Response();
+ }
+
+ //if the user can see it, generate a token;
+ //redirect to the alt domain with the token;
+ return id(new AphrontRedirectResponse())
+ ->setURI($file->getCDNURIWithToken());
+
+ } else {
+ //load the file, bypassing permission checks;
+ $file = id(new PhabricatorFileQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withPHIDs(array($this->phid))
+ ->executeOne();
+
+ if (!$file) {
+ return new Aphront404Response();
+ }
+
+ if ($this->token) {
+ //validate the token, if it is valid, continue
+ if (! $validated_token = $file->validateOneTimeToken($this->token)) {
+ return new Aphront403Response();
+ }
+ //return the file data without cache headers
+ $cache_response = false;
+ } else if (!$file->getCanCDN()) {
+ // file cannot be served via cdn, and no token given
+ // deny access...
+ return new Aphront403Response();
+ }
+ }
+
if (!$file) {
return new Aphront404Response();
}
@@ -44,7 +92,9 @@
$data = $file->loadFileData();
$response = new AphrontFileResponse();
$response->setContent($data);
- $response->setCacheDurationInSeconds(60 * 60 * 24 * 30);
+ if ($cache_response) {
+ $response->setCacheDurationInSeconds(60 * 60 * 24 * 30);
+ }
// NOTE: It's important to accept "Range" requests when playing audio.
// If we don't, Safari has difficulty figuring out how long sounds are
@@ -58,6 +108,8 @@
$response->setHTTPResponseCode(206);
$response->setRange((int)$matches[1], (int)$matches[2]);
}
+ } else if (isset($validated_token)) {
+ $validated_token->delete();
}
$is_viewable = $file->isViewableInBrowser();
diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php
--- a/src/applications/files/storage/PhabricatorFile.php
+++ b/src/applications/files/storage/PhabricatorFile.php
@@ -7,10 +7,12 @@
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_NO_CDN = 'nocdn';
protected $name;
protected $mimeType;
@@ -198,7 +200,6 @@
}
private static function buildFromFileData($data, array $params = array()) {
- $selector = PhabricatorEnv::newObjectFromConfig('storage.engine-selector');
if (isset($params['storageEngines'])) {
$engines = $params['storageEngines'];
@@ -848,6 +849,58 @@
return idx($this->metadata, self::METADATA_IMAGE_WIDTH);
}
+ public function getCanCDN() {
+ if (!$this->isViewableImage()) {
+ return false;
+ }
+ return idx($this->metadata, self::METADATA_CAN_CDN);
+ }
+
+ 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.
*

File Metadata

Mime Type
text/plain
Expires
Sat, Apr 12, 3:13 AM (1 d, 22 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7729664
Default Alt Text
D10054.id24232.diff (7 KB)

Event Timeline