Page MenuHomePhabricator

D10054.id24280.diff
No OneTemporary

D10054.id24280.diff

diff --git a/resources/sql/autopatches/20140731.cancdn.php b/resources/sql/autopatches/20140731.cancdn.php
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20140731.cancdn.php
@@ -0,0 +1,20 @@
+<?php
+
+$table = new PhabricatorFile();
+$conn_w = $table->establishConnection('w');
+foreach (new LiskMigrationIterator($table) as $file) {
+ $id = $file->getID();
+ echo "Updating flags for file {$id}...\n";
+ $meta = $file->getMetadata();
+ if (!idx($meta, 'canCDN')) {
+
+ $meta['canCDN'] = true;
+
+ queryfx(
+ $conn_w,
+ 'UPDATE %T SET metadata = %s WHERE id = %d',
+ $table->getTableName(),
+ json_encode($meta),
+ $id);
+ }
+}
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,79 @@
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');
+ $base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
+ $alt_uri = new PhutilURI($alt);
+ $alt_domain = $alt_uri->getDomain();
+ $req_domain = $request->getHost();
+ $main_domain = id(new PhutilURI($base_uri))->getDomain();
+
+ $cache_response = true;
+
+ if (empty($alt) || $main_domain == $alt_domain) {
+ // Alternate files domain isn't configured or it's set
+ // to the same as the default domain
+
+ // load the file with permissions checks;
+ $file = id(new PhabricatorFileQuery())
+ ->setViewer($request->getUser())
+ ->withPHIDs(array($this->phid))
+ ->executeOne();
+ // when the file is not CDNable, don't allow cache
+ $cache_response = $file && $file->getCanCDN();
+ } else if ($req_domain != $alt_domain) {
+ // Alternate domain is configured but this request isn't using it
+
+ // 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 the file, generate a token;
+ // redirect to the alt domain with the token;
+ return id(new AphrontRedirectResponse())
+ ->setURI($file->getCDNURIWithToken());
+
+ } else {
+ // We are using the alternate domain
+
+ // 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
+ // redirect to the main domain to aquire a token
+ $file_uri = id(new PhutilURI($file->getViewURI()))
+ ->setDomain($main_domain);
+
+ return id(new AphrontRedirectResponse())
+ ->setURI($file_uri);
+ }
+ }
+
if (!$file) {
return new Aphront404Response();
}
@@ -44,7 +103,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 +119,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_CAN_CDN = 'cancdn';
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
Wed, Mar 5, 3:27 AM (6 d, 23 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7223463
Default Alt Text
D10054.id24280.diff (8 KB)

Event Timeline