Index: resources/sql/patches/20131210.filenonce.sql =================================================================== --- /dev/null +++ resources/sql/patches/20131210.filenonce.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_file.file_nonce ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + filePHID VARCHAR(64) NOT NULL COLLATE utf8_bin, + nonce VARCHAR(32) NOT NULL COLLATE utf8_bin, + dateExpiry INT UNSIGNED NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_nonce` (filePHID, nonce) +) ENGINE=InnoDB, COLLATE utf8_general_ci; Index: src/__phutil_library_map__.php =================================================================== --- src/__phutil_library_map__.php +++ src/__phutil_library_map__.php @@ -1413,6 +1413,7 @@ 'PhabricatorFileLinkListView' => 'view/layout/PhabricatorFileLinkListView.php', 'PhabricatorFileLinkView' => 'view/layout/PhabricatorFileLinkView.php', 'PhabricatorFileListController' => 'applications/files/controller/PhabricatorFileListController.php', + 'PhabricatorFileNonce' => 'applications/files/storage/PhabricatorFileNonce.php', 'PhabricatorFilePHIDTypeFile' => 'applications/files/phid/PhabricatorFilePHIDTypeFile.php', 'PhabricatorFileQuery' => 'applications/files/query/PhabricatorFileQuery.php', 'PhabricatorFileSearchEngine' => 'applications/files/query/PhabricatorFileSearchEngine.php', @@ -2176,6 +2177,7 @@ 'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php', 'PhortuneWePayPaymentProvider' => 'applications/phortune/provider/PhortuneWePayPaymentProvider.php', 'PhragmentBrowseController' => 'applications/phragment/controller/PhragmentBrowseController.php', + 'PhragmentCapabilityCanCreate' => 'applications/phragment/capability/PhragmentCapabilityCanCreate.php', 'PhragmentController' => 'applications/phragment/controller/PhragmentController.php', 'PhragmentCreateController' => 'applications/phragment/controller/PhragmentCreateController.php', 'PhragmentDAO' => 'applications/phragment/storage/PhragmentDAO.php', @@ -2189,6 +2191,7 @@ 'PhragmentPHIDTypeSnapshot' => 'applications/phragment/phid/PhragmentPHIDTypeSnapshot.php', 'PhragmentPatchController' => 'applications/phragment/controller/PhragmentPatchController.php', 'PhragmentPatchUtil' => 'applications/phragment/util/PhragmentPatchUtil.php', + 'PhragmentPolicyController' => 'applications/phragment/controller/PhragmentPolicyController.php', 'PhragmentRevertController' => 'applications/phragment/controller/PhragmentRevertController.php', 'PhragmentSnapshot' => 'applications/phragment/storage/PhragmentSnapshot.php', 'PhragmentSnapshotChild' => 'applications/phragment/storage/PhragmentSnapshotChild.php', @@ -3932,6 +3935,7 @@ 0 => 'PhabricatorFileController', 1 => 'PhabricatorApplicationSearchResultsControllerInterface', ), + 'PhabricatorFileNonce' => 'PhabricatorFileDAO', 'PhabricatorFilePHIDTypeFile' => 'PhabricatorPHIDType', 'PhabricatorFileQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorFileSearchEngine' => 'PhabricatorApplicationSearchEngine', @@ -4782,6 +4786,7 @@ 'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider', 'PhortuneWePayPaymentProvider' => 'PhortunePaymentProvider', 'PhragmentBrowseController' => 'PhragmentController', + 'PhragmentCapabilityCanCreate' => 'PhabricatorPolicyCapability', 'PhragmentController' => 'PhabricatorController', 'PhragmentCreateController' => 'PhragmentController', 'PhragmentDAO' => 'PhabricatorLiskDAO', @@ -4803,6 +4808,7 @@ 'PhragmentPHIDTypeSnapshot' => 'PhabricatorPHIDType', 'PhragmentPatchController' => 'PhragmentController', 'PhragmentPatchUtil' => 'Phobject', + 'PhragmentPolicyController' => 'PhragmentController', 'PhragmentRevertController' => 'PhragmentController', 'PhragmentSnapshot' => array( Index: src/applications/files/controller/PhabricatorFileDataController.php =================================================================== --- src/applications/files/controller/PhabricatorFileDataController.php +++ src/applications/files/controller/PhabricatorFileDataController.php @@ -67,13 +67,34 @@ $response->setMimeType($file->getViewableMimeType()); } else { if (!$request->isHTTPPost()) { + // Check if a nonce has been provided. This is used so that + // Phabricator can redirect users to the direct download without + // going through a POST submission (e.g. when a controller does + // a simple redirect). These tokens have a short life time (60 + // seconds), can only be used once, and can only be generated from + // within Phabricator itself. + $nonce_exists = false; + if ($request->getExists('nonce')) { + $token = $request->getStr('nonce'); + $nonce = id(new PhabricatorFileNonce())->loadOneWhere( + 'filePHID = %s AND nonce = %s', + $file->getPHID(), + $request->getStr('nonce')); + if ($nonce !== null) { + $nonce->delete(); + $nonce_exists = true; + } + } + // NOTE: Require POST to download files. We'd rather go full-bore and // do a real CSRF check, but can't currently authenticate users on the // file domain. This should blunt any attacks based on iframes, script // tags, applet tags, etc., at least. Send the user to the "info" page // if they're using some other method. - return id(new AphrontRedirectResponse()) - ->setURI(PhabricatorEnv::getProductionURI($file->getBestURI())); + if (!$nonce_exists) { + return id(new AphrontRedirectResponse()) + ->setURI(PhabricatorEnv::getProductionURI($file->getBestURI())); + } } $response->setMimeType($file->getMimeType()); $response->setDownload($file->getName()); Index: src/applications/files/storage/PhabricatorFile.php =================================================================== --- src/applications/files/storage/PhabricatorFile.php +++ src/applications/files/storage/PhabricatorFile.php @@ -512,6 +512,30 @@ return (string) $uri; } + /** + * Provides a download URI to the file with a one-time use token to permit + * GET requests. This should be used when redirecting the user to download + * the file directly. + * + * The expiry time is set to 60 seconds, as these URIs are not expected to + * be long lived. + */ + public function getNonceURI($expiry = 60) { + $token = Filesystem::readRandomCharacters(32); + + $nonce = id(new PhabricatorFileNonce()); + $nonce->setFilePHID($this->getPHID()); + $nonce->setNonce($token); + $nonce->setDateExpiry(time() + $expiry); + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $nonce->save(); + unset($unguarded); + + $uri = id(new PhutilURI($this->getViewURI())) + ->setQueryParam('nonce', $token); + return (string) $uri; + } + public function getProfileThumbURI() { $path = '/file/xform/thumb-profile/'.$this->getPHID().'/' .$this->getSecretKey().'/'; @@ -877,8 +901,12 @@ } public function getPolicy($capability) { - // TODO: Implement proper per-object policies. - return PhabricatorPolicies::POLICY_USER; + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return $this->getViewPolicy(); + default: + return PhabricatorPolicies::POLICY_USER; + } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { Index: src/applications/files/storage/PhabricatorFileNonce.php =================================================================== --- /dev/null +++ src/applications/files/storage/PhabricatorFileNonce.php @@ -0,0 +1,19 @@ +assertAttached($this->file); + } + + public function attachFile(PhabricatorFile $file) { + return $this->file = $file; + } + +} Index: src/applications/phragment/application/PhabricatorApplicationPhragment.php =================================================================== --- src/applications/phragment/application/PhabricatorApplicationPhragment.php +++ src/applications/phragment/application/PhabricatorApplicationPhragment.php @@ -37,6 +37,7 @@ 'browse/(?P.*)' => 'PhragmentBrowseController', 'create/(?P.*)' => 'PhragmentCreateController', 'update/(?P.*)' => 'PhragmentUpdateController', + 'policy/(?P.*)' => 'PhragmentPolicyController', 'history/(?P.*)' => 'PhragmentHistoryController', 'zip/(?P.*)' => 'PhragmentZIPController', 'zip@(?P[^/]+)/(?P.*)' => 'PhragmentZIPController', @@ -56,5 +57,12 @@ ); } + protected function getCustomCapabilities() { + return array( + PhragmentCapabilityCanCreate::CAPABILITY => array( + ), + ); + } + } Index: src/applications/phragment/capability/PhragmentCapabilityCanCreate.php =================================================================== --- /dev/null +++ src/applications/phragment/capability/PhragmentCapabilityCanCreate.php @@ -0,0 +1,20 @@ +dblob = idx($data, "dblob", ""); } @@ -24,11 +28,14 @@ } $crumbs = $this->buildApplicationCrumbsWithPath($parents); - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create Fragment')) - ->setHref($this->getApplicationURI('/create/'.$path)) - ->setIcon('create')); + if ($this->hasApplicationCapability( + PhragmentCapabilityCanCreate::CAPABILITY)) { + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Create Fragment')) + ->setHref($this->getApplicationURI('/create/'.$path)) + ->setIcon('create')); + } $current_box = $this->createCurrentFragmentView($current, false); Index: src/applications/phragment/controller/PhragmentController.php =================================================================== --- src/applications/phragment/controller/PhragmentController.php +++ src/applications/phragment/controller/PhragmentController.php @@ -79,12 +79,16 @@ $file = null; $file_uri = null; if (!$fragment->isDirectory()) { - $file = id(new PhabricatorFileQuery()) - ->setViewer($viewer) - ->withPHIDs(array($fragment->getLatestVersion()->getFilePHID())) - ->executeOne(); - if ($file !== null) { - $file_uri = $file->getBestURI(); + try { + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($fragment->getLatestVersion()->getFilePHID())) + ->executeOne(); + if ($file !== null) { + $file_uri = $file->getDownloadURI(); + } + } catch (PhabricatorPolicyException $ex) { + // The download link won't be usable. } } @@ -93,6 +97,11 @@ ->setPolicyObject($fragment) ->setUser($viewer); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $fragment, + PhabricatorPolicyCapability::CAN_EDIT); + $actions = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObject($fragment) @@ -102,28 +111,39 @@ ->setName(pht('Download Fragment')) ->setHref($file_uri) ->setDisabled($file === null) + ->setRenderAsForm(true) + ->setDownload(true) + ->setAnonymous(true) ->setIcon('download')); $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Download Contents as ZIP')) ->setHref($this->getApplicationURI("zip/".$fragment->getPath())) - ->setDisabled(false) // TODO: Policy ->setIcon('zip')); if (!$fragment->isDirectory()) { $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Update Fragment')) ->setHref($this->getApplicationURI("update/".$fragment->getPath())) - ->setDisabled(false) // TODO: Policy + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) ->setIcon('edit')); } else { $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Convert to File')) ->setHref($this->getApplicationURI("update/".$fragment->getPath())) - ->setDisabled(false) // TODO: Policy + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) ->setIcon('edit')); } + $actions->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Set Fragment Policies')) + ->setHref($this->getApplicationURI("policy/".$fragment->getPath())) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setIcon('edit')); if ($is_history_view) { $actions->addAction( id(new PhabricatorActionView()) @@ -142,7 +162,8 @@ ->setName(pht('Create Snapshot')) ->setHref($this->getApplicationURI( "snapshot/create/".$fragment->getPath())) - ->setDisabled(false) // TODO: Policy + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) ->setIcon('snapshot')); $actions->addAction( id(new PhabricatorActionView()) @@ -150,7 +171,8 @@ ->setHref($this->getApplicationURI( "snapshot/promote/latest/".$fragment->getPath())) ->setWorkflow(true) - ->setDisabled(false) // TODO: Policy + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) ->setIcon('promote')); $properties = id(new PHUIPropertyListView()) Index: src/applications/phragment/controller/PhragmentHistoryController.php =================================================================== --- src/applications/phragment/controller/PhragmentHistoryController.php +++ src/applications/phragment/controller/PhragmentHistoryController.php @@ -4,6 +4,10 @@ private $dblob; + public function shouldAllowPublic() { + return true; + } + public function willProcessRequest(array $data) { $this->dblob = idx($data, "dblob", ""); } @@ -21,11 +25,14 @@ $path = $current->getPath(); $crumbs = $this->buildApplicationCrumbsWithPath($parents); - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create Fragment')) - ->setHref($this->getApplicationURI('/create/'.$path)) - ->setIcon('create')); + if ($this->hasApplicationCapability( + PhragmentCapabilityCanCreate::CAPABILITY)) { + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Create Fragment')) + ->setHref($this->getApplicationURI('/create/'.$path)) + ->setIcon('create')); + } $current_box = $this->createCurrentFragmentView($current, true); @@ -44,6 +51,11 @@ ->execute(); $files = mpull($files, null, 'getPHID'); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $current, + PhabricatorPolicyCapability::CAN_EDIT); + $first = true; foreach ($versions as $version) { $item = id(new PHUIObjectItemView()); @@ -58,7 +70,7 @@ $item->addAttribute('Deletion'); } - if (!$first) { + if (!$first && $can_edit) { $item->addAction(id(new PHUIListItemView()) ->setIcon('undo') ->setRenderNameAsTooltip(true) @@ -68,16 +80,22 @@ "revert/".$version->getID()."/".$current->getPath()))); } - $disabled = !isset($files[$version->getFilePHID()]); - $action = id(new PHUIListItemView()) - ->setIcon('download') - ->setDisabled($disabled) - ->setRenderNameAsTooltip(true) - ->setName(pht("Download")); - if (!$disabled) { - $action->setHref($files[$version->getFilePHID()]->getBestURI()); + // We can't display the download links here for unauthenticated users + // as they won't be able to see the info page. If they click through + // to the version they can download it from there anyway. + if (!$request->getUser()) { + $disabled = !isset($files[$version->getFilePHID()]); + $action = id(new PHUIListItemView()) + ->setIcon('download') + ->setDisabled($disabled) + ->setRenderNameAsTooltip(true) + ->setName(pht("Download")); + if (!$disabled) { + $action->setHref($files[$version->getFilePHID()]->getBestURI()); + } + $item->addAction($action); } - $item->addAction($action); + $list->addItem($item); $first = false; Index: src/applications/phragment/controller/PhragmentPatchController.php =================================================================== --- src/applications/phragment/controller/PhragmentPatchController.php +++ src/applications/phragment/controller/PhragmentPatchController.php @@ -5,6 +5,10 @@ private $aid; private $bid; + public function shouldAllowPublic() { + return true; + } + public function willProcessRequest(array $data) { $this->aid = idx($data, "aid", 0); $this->bid = idx($data, "bid", 0); @@ -61,7 +65,9 @@ $patch = PhragmentPatchUtil::calculatePatch($file_a, $file_b); if ($patch === null) { - throw new Exception("Unable to compute patch!"); + // There are no differences between the two files, so we output + // an empty patch. + $patch = ''; } $a_sequence = 'x'; @@ -81,6 +87,9 @@ 'mime-type' => 'text/plain', 'ttl' => time() + 60 * 60 * 24, )); + + // FIXME: Because Phabricator will deduplicate the file above, the patch + // won't necessarily be visible to the user and they'll get a login page :( return id(new AphrontRedirectResponse()) ->setURI($result->getBestURI()); } Index: src/applications/phragment/controller/PhragmentPolicyController.php =================================================================== --- /dev/null +++ src/applications/phragment/controller/PhragmentPolicyController.php @@ -0,0 +1,151 @@ +dblob = idx($data, "dblob", ""); + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $parents = $this->loadParentFragments($this->dblob); + if ($parents === null) { + return new Aphront404Response(); + } + $fragment = idx($parents, count($parents) - 1, null); + + $error_view = null; + + if ($request->isFormPost()) { + $errors = array(); + + $v_view_policy = $request->getStr('viewPolicy'); + $v_edit_policy = $request->getStr('editPolicy'); + $v_replace_children = $request->getBool('replacePoliciesOnChildren'); + $v_set_file_policy = $request->getBool('setFilePolicies'); + + $fragment->setViewPolicy($v_view_policy); + $fragment->setEditPolicy($v_edit_policy); + + $fragment->save(); + + if ($v_set_file_policy) { + $versions = id(new PhragmentFragmentVersionQuery()) + ->setViewer($viewer) + ->withFragmentPHIDs(array($fragment->getPHID())) + ->execute(); + $file_phids = mpull($versions, 'getFilePHID'); + + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs($file_phids) + ->execute(); + foreach ($files as $file) { + $file->setViewPolicy($v_view_policy); + $file->save(); + } + } + + if ($v_replace_children) { + // If you can edit a fragment, you can forcibly set the policies + // on child fragments, regardless of whether you can see them or not. + $children = id(new PhragmentFragmentQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withLeadingPath($fragment->getPath().'/') + ->execute(); + $children_phids = mpull($children, 'getPHID'); + + $fragment->openTransaction(); + foreach ($children as $child) { + $child->setViewPolicy($v_view_policy); + $child->setEditPolicy($v_edit_policy); + $child->save(); + } + $fragment->saveTransaction(); + + if ($v_set_file_policy) { + $versions = id(new PhragmentFragmentVersionQuery()) + ->setViewer($viewer) + ->withFragmentPHIDs($children_phids) + ->execute(); + $file_phids = mpull($versions, 'getFilePHID'); + + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs($file_phids) + ->execute(); + foreach ($files as $file) { + $file->setViewPolicy($v_view_policy); + $file->save(); + } + } + } + + return id(new AphrontRedirectResponse()) + ->setURI('/phragment/browse/'.$fragment->getPath()); + } + + $policies = id(new PhabricatorPolicyQuery()) + ->setViewer($viewer) + ->setObject($fragment) + ->execute(); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new AphrontFormPolicyControl()) + ->setName('viewPolicy') + ->setPolicyObject($fragment) + ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) + ->setPolicies($policies)) + ->appendChild( + id(new AphrontFormPolicyControl()) + ->setName('editPolicy') + ->setPolicyObject($fragment) + ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) + ->setPolicies($policies)) + ->appendChild( + id(new AphrontFormCheckboxControl()) + ->addCheckbox( + 'replacePoliciesOnChildren', + 'true', + pht( + 'Replace policies on child fragments with '. + 'the policies above.')) + ->addCheckbox( + 'setFilePolicies', + 'true', + pht( + 'Replace policies on files associated with all versions '. + 'of this fragment. You must have view access to the files '. + 'to be able to set their policies.'))) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Save Fragment Policies')) + ->addCancelButton( + $this->getApplicationURI('browse/'.$fragment->getPath()))); + + $crumbs = $this->buildApplicationCrumbsWithPath($parents); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('Edit Fragment Policies'))); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Edit Fragment Policies: %s', $fragment->getPath())) + ->setValidationException(null) + ->setForm($form); + + return $this->buildApplicationPage( + array( + $crumbs, + $box), + array( + 'title' => pht('Edit Fragment Policies'), + 'device' => true)); + } + +} Index: src/applications/phragment/controller/PhragmentSnapshotCreateController.php =================================================================== --- src/applications/phragment/controller/PhragmentSnapshotCreateController.php +++ src/applications/phragment/controller/PhragmentSnapshotCreateController.php @@ -21,6 +21,11 @@ return new Aphront404Response(); } + PhabricatorPolicyFilter::requireCapability( + $viewer, + $fragment, + PhabricatorPolicyCapability::CAN_EDIT); + $children = id(new PhragmentFragmentQuery()) ->setViewer($viewer) ->needLatestVersion(true) Index: src/applications/phragment/controller/PhragmentSnapshotDeleteController.php =================================================================== --- src/applications/phragment/controller/PhragmentSnapshotDeleteController.php +++ src/applications/phragment/controller/PhragmentSnapshotDeleteController.php @@ -20,6 +20,11 @@ return new Aphront404Response(); } + PhabricatorPolicyFilter::requireCapability( + $viewer, + $snapshot, + PhabricatorPolicyCapability::CAN_EDIT); + if ($request->isDialogFormPost()) { $fragment_uri = $snapshot->getPrimaryFragment()->getURI(); Index: src/applications/phragment/controller/PhragmentSnapshotPromoteController.php =================================================================== --- src/applications/phragment/controller/PhragmentSnapshotPromoteController.php +++ src/applications/phragment/controller/PhragmentSnapshotPromoteController.php @@ -29,6 +29,11 @@ return new Aphront404Response(); } + PhabricatorPolicyFilter::requireCapability( + $viewer, + $this->targetFragment, + PhabricatorPolicyCapability::CAN_EDIT); + $this->snapshots = id(new PhragmentSnapshotQuery()) ->setViewer($viewer) ->withPrimaryFragmentPHIDs(array($this->targetFragment->getPHID())) @@ -46,6 +51,11 @@ return new Aphront404Response(); } + PhabricatorPolicyFilter::requireCapability( + $viewer, + $this->targetSnapshot, + PhabricatorPolicyCapability::CAN_EDIT); + $this->snapshots = id(new PhragmentSnapshotQuery()) ->setViewer($viewer) ->withPrimaryFragmentPHIDs(array( Index: src/applications/phragment/controller/PhragmentSnapshotViewController.php =================================================================== --- src/applications/phragment/controller/PhragmentSnapshotViewController.php +++ src/applications/phragment/controller/PhragmentSnapshotViewController.php @@ -4,6 +4,10 @@ private $id; + public function shouldAllowPublic() { + return true; + } + public function willProcessRequest(array $data) { $this->id = idx($data, "id", ""); } Index: src/applications/phragment/controller/PhragmentVersionController.php =================================================================== --- src/applications/phragment/controller/PhragmentVersionController.php +++ src/applications/phragment/controller/PhragmentVersionController.php @@ -4,6 +4,10 @@ private $id; + public function shouldAllowPublic() { + return true; + } + public function willProcessRequest(array $data) { $this->id = idx($data, "id", 0); } @@ -41,7 +45,7 @@ ->withPHIDs(array($version->getFilePHID())) ->executeOne(); if ($file !== null) { - $file_uri = $file->getBestURI(); + $file_uri = $file->getDownloadURI(); } $header = id(new PHUIHeaderView()) @@ -61,6 +65,9 @@ ->setName(pht('Download Version')) ->setHref($file_uri) ->setDisabled($file === null) + ->setRenderAsForm(true) + ->setDownload(true) + ->setAnonymous(true) ->setIcon('download')); $properties = id(new PHUIPropertyListView()) Index: src/applications/phragment/controller/PhragmentZIPController.php =================================================================== --- src/applications/phragment/controller/PhragmentZIPController.php +++ src/applications/phragment/controller/PhragmentZIPController.php @@ -7,6 +7,10 @@ private $snapshotCache; + public function shouldAllowPublic() { + return true; + } + public function willProcessRequest(array $data) { $this->dblob = idx($data, "dblob", ""); $this->snapshot = idx($data, "snapshot", null); @@ -87,7 +91,9 @@ } foreach ($mappings as $path => $file) { - $zip->addFromString($path, $file->loadFileData()); + if ($file !== null) { + $zip->addFromString($path, $file->loadFileData()); + } } $zip->close(); @@ -104,7 +110,7 @@ 'ttl' => time() + 60 * 60 * 24, )); return id(new AphrontRedirectResponse()) - ->setURI($file->getBestURI()); + ->setURI($file->getNonceURI()); } /** Index: src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php =================================================================== --- src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -1828,6 +1828,10 @@ 'type' => 'sql', 'name' => $this->getPatchPath('20131208.phragmentsnapshot.sql'), ), + '20131210.filenonce.sql' => array( + 'type' => 'sql', + 'name' => $this->getPatchPath('20131210.filenonce.sql'), + ), ); } } Index: src/view/layout/PhabricatorActionView.php =================================================================== --- src/view/layout/PhabricatorActionView.php +++ src/view/layout/PhabricatorActionView.php @@ -11,6 +11,7 @@ private $renderAsForm; private $download; private $objectURI; + private $anonymous; public function setObjectURI($object_uri) { $this->objectURI = $object_uri; @@ -40,7 +41,7 @@ * viewing. */ public function getHref() { - if ($this->workflow || $this->renderAsForm) { + if (($this->workflow || $this->renderAsForm) && !$this->anonymous) { if (!$this->user || !$this->user->isLoggedIn()) { return id(new PhutilURI('/auth/start/')) ->setQueryParam('next', (string)$this->getObjectURI()); @@ -75,6 +76,11 @@ return $this; } + public function setAnonymous($anonymous) { + $this->anonymous = $anonymous; + return $this; + } + public function setRenderAsForm($form) { $this->renderAsForm = $form; return $this;