1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-29 10:12:41 +01:00

Implement policies in Phragment

Summary: This implements support for enforcing and setting policies in Phragment.

Test Plan: Set policies and ensured they were enforced successfully.

Reviewers: epriestley, #blessed_reviewers

Reviewed By: epriestley

CC: Korvin, epriestley, aran

Maniphest Tasks: T4205

Differential Revision: https://secure.phabricator.com/D7751
This commit is contained in:
James Rhodes 2013-12-13 14:42:12 +11:00
parent 7f45824984
commit 86ec4d6021
23 changed files with 350 additions and 47 deletions

View file

@ -0,0 +1,15 @@
CREATE TABLE {$NAMESPACE}_phragment.edge (
src VARCHAR(64) NOT NULL COLLATE utf8_bin,
type VARCHAR(64) NOT NULL COLLATE utf8_bin,
dst VARCHAR(64) NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
seq INT UNSIGNED NOT NULL,
dataID INT UNSIGNED,
PRIMARY KEY (src, type, dst),
KEY (src, type, dateCreated, seq)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_phragment.edgedata (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
data LONGTEXT NOT NULL COLLATE utf8_bin
) ENGINE=InnoDB, COLLATE utf8_general_ci;

View file

@ -2180,6 +2180,7 @@ phutil_register_library_map(array(
'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',
@ -2193,6 +2194,7 @@ phutil_register_library_map(array(
'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',
@ -4790,6 +4792,7 @@ phutil_register_library_map(array(
'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider',
'PhortuneWePayPaymentProvider' => 'PhortunePaymentProvider',
'PhragmentBrowseController' => 'PhragmentController',
'PhragmentCapabilityCanCreate' => 'PhabricatorPolicyCapability',
'PhragmentController' => 'PhabricatorController',
'PhragmentCreateController' => 'PhragmentController',
'PhragmentDAO' => 'PhabricatorLiskDAO',
@ -4811,6 +4814,7 @@ phutil_register_library_map(array(
'PhragmentPHIDTypeSnapshot' => 'PhabricatorPHIDType',
'PhragmentPatchController' => 'PhragmentController',
'PhragmentPatchUtil' => 'Phobject',
'PhragmentPolicyController' => 'PhragmentController',
'PhragmentRevertController' => 'PhragmentController',
'PhragmentSnapshot' =>
array(

View file

@ -61,6 +61,7 @@ final class PhabricatorApplicationFiles extends PhabricatorApplication {
'xform/(?P<transform>[^/]+)/(?P<phid>[^/]+)/(?P<key>[^/]+)/'
=> 'PhabricatorFileTransformController',
'uploaddialog/' => 'PhabricatorFileUploadDialogController',
'download/(?P<phid>[^/]+)/' => 'PhabricatorFileDialogController',
),
);
}

View file

@ -66,12 +66,12 @@ final class PhabricatorFileDataController extends PhabricatorFileController {
if ($is_viewable && !$force_download) {
$response->setMimeType($file->getViewableMimeType());
} else {
if (!$request->isHTTPPost()) {
// 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.
if (!$request->isHTTPPost() && !$alt_domain) {
// NOTE: Require POST to download files from the primary domain. 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()));
}

View file

@ -37,6 +37,7 @@ final class PhabricatorApplicationPhragment extends PhabricatorApplication {
'browse/(?P<dblob>.*)' => 'PhragmentBrowseController',
'create/(?P<dblob>.*)' => 'PhragmentCreateController',
'update/(?P<dblob>.*)' => 'PhragmentUpdateController',
'policy/(?P<dblob>.*)' => 'PhragmentPolicyController',
'history/(?P<dblob>.*)' => 'PhragmentHistoryController',
'zip/(?P<dblob>.*)' => 'PhragmentZIPController',
'zip@(?P<snapshot>[^/]+)/(?P<dblob>.*)' => 'PhragmentZIPController',
@ -56,5 +57,12 @@ final class PhabricatorApplicationPhragment extends PhabricatorApplication {
);
}
protected function getCustomCapabilities() {
return array(
PhragmentCapabilityCanCreate::CAPABILITY => array(
),
);
}
}

View file

@ -0,0 +1,20 @@
<?php
final class PhragmentCapabilityCanCreate
extends PhabricatorPolicyCapability {
const CAPABILITY = 'phragment.create';
public function getCapabilityKey() {
return self::CAPABILITY;
}
public function getCapabilityName() {
return pht('Can Create Fragments');
}
public function describeCapabilityRejection() {
return pht('You do not have permission to create fragments.');
}
}

View file

@ -4,6 +4,10 @@ final class PhragmentBrowseController extends PhragmentController {
private $dblob;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->dblob = idx($data, "dblob", "");
}
@ -24,11 +28,14 @@ final class PhragmentBrowseController extends PhragmentController {
}
$crumbs = $this->buildApplicationCrumbsWithPath($parents);
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);
@ -79,6 +86,7 @@ final class PhragmentBrowseController extends PhragmentController {
return $this->buildApplicationPage(
array(
$crumbs,
$this->renderConfigurationWarningIfRequired(),
$current_box,
$list),
array(

View file

@ -84,7 +84,7 @@ abstract class PhragmentController extends PhabricatorController {
->withPHIDs(array($fragment->getLatestVersion()->getFilePHID()))
->executeOne();
if ($file !== null) {
$file_uri = $file->getBestURI();
$file_uri = $file->getDownloadURI();
}
}
@ -93,6 +93,13 @@ abstract class PhragmentController extends PhabricatorController {
->setPolicyObject($fragment)
->setUser($viewer);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$fragment,
PhabricatorPolicyCapability::CAN_EDIT);
$zip_uri = $this->getApplicationURI("zip/".$fragment->getPath());
$actions = id(new PhabricatorActionListView())
->setUser($viewer)
->setObject($fragment)
@ -100,30 +107,39 @@ abstract class PhragmentController extends PhabricatorController {
$actions->addAction(
id(new PhabricatorActionView())
->setName(pht('Download Fragment'))
->setHref($file_uri)
->setDisabled($file === null)
->setHref($this->isCorrectlyConfigured() ? $file_uri : null)
->setDisabled($file === null || !$this->isCorrectlyConfigured())
->setIcon('download'));
$actions->addAction(
id(new PhabricatorActionView())
->setName(pht('Download Contents as ZIP'))
->setHref($this->getApplicationURI("zip/".$fragment->getPath()))
->setDisabled(false) // TODO: Policy
->setHref($this->isCorrectlyConfigured() ? $zip_uri : null)
->setDisabled(!$this->isCorrectlyConfigured())
->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 +158,8 @@ abstract class PhragmentController extends PhabricatorController {
->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 +167,7 @@ abstract class PhragmentController extends PhabricatorController {
->setHref($this->getApplicationURI(
"snapshot/promote/latest/".$fragment->getPath()))
->setWorkflow(true)
->setDisabled(false) // TODO: Policy
->setDisabled(!$can_edit)
->setIcon('promote'));
$properties = id(new PHUIPropertyListView())
@ -188,4 +205,33 @@ abstract class PhragmentController extends PhabricatorController {
->addPropertyList($properties);
}
function renderConfigurationWarningIfRequired() {
$alt = PhabricatorEnv::getEnvConfig("security.alternate-file-domain");
if ($alt === null) {
return id(new AphrontErrorView())
->setTitle(pht('security.alternate-file-domain must be configured!'))
->setSeverity(AphrontErrorView::SEVERITY_ERROR)
->appendChild(phutil_tag('p', array(), pht(
'Because Phragment generates files (such as ZIP archives and '.
'patches) as they are requested, it requires that you configure '.
'the `security.alterate-file-domain` option. This option on it\'s '.
'own will also provide additional security when serving files '.
'across Phabricator.')));
}
return null;
}
/**
* We use this to disable the download links if the alternate domain is
* not configured correctly. Although the download links will mostly work
* for logged in users without an alternate domain, the behaviour is
* reasonably non-consistent and will deny public users, even if policies
* are configured otherwise (because the Files app does not support showing
* the info page to viewers who are not logged in).
*/
function isCorrectlyConfigured() {
$alt = PhabricatorEnv::getEnvConfig("security.alternate-file-domain");
return $alt !== null;
}
}

View file

@ -123,6 +123,7 @@ final class PhragmentCreateController extends PhragmentController {
return $this->buildApplicationPage(
array(
$crumbs,
$this->renderConfigurationWarningIfRequired(),
$box),
array(
'title' => pht('Create Fragment'),

View file

@ -4,6 +4,10 @@ final class PhragmentHistoryController extends PhragmentController {
private $dblob;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->dblob = idx($data, "dblob", "");
}
@ -21,11 +25,14 @@ final class PhragmentHistoryController extends PhragmentController {
$path = $current->getPath();
$crumbs = $this->buildApplicationCrumbsWithPath($parents);
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 @@ final class PhragmentHistoryController extends PhragmentController {
->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 @@ final class PhragmentHistoryController extends PhragmentController {
$item->addAttribute('Deletion');
}
if (!$first) {
if (!$first && $can_edit) {
$item->addAction(id(new PHUIListItemView())
->setIcon('undo')
->setRenderNameAsTooltip(true)
@ -71,13 +83,15 @@ final class PhragmentHistoryController extends PhragmentController {
$disabled = !isset($files[$version->getFilePHID()]);
$action = id(new PHUIListItemView())
->setIcon('download')
->setDisabled($disabled)
->setDisabled($disabled || !$this->isCorrectlyConfigured())
->setRenderNameAsTooltip(true)
->setName(pht("Download"));
if (!$disabled) {
$action->setHref($files[$version->getFilePHID()]->getBestURI());
if (!$disabled && $this->isCorrectlyConfigured()) {
$action->setHref($files[$version->getFilePHID()]
->getDownloadURI($version->getURI()));
}
$item->addAction($action);
$list->addItem($item);
$first = false;
@ -86,6 +100,7 @@ final class PhragmentHistoryController extends PhragmentController {
return $this->buildApplicationPage(
array(
$crumbs,
$this->renderConfigurationWarningIfRequired(),
$current_box,
$list),
array(

View file

@ -5,6 +5,10 @@ final class PhragmentPatchController extends PhragmentController {
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 @@ final class PhragmentPatchController extends PhragmentController {
$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';
@ -74,6 +80,11 @@ final class PhragmentPatchController extends PhragmentController {
$a_sequence.'.'.
$version_b->getSequence().'.patch';
$return = $version_b->getURI();
if ($request->getExists('return')) {
$return = $request->getStr('return');
}
$result = PhabricatorFile::buildFromFileDataOrHash(
$patch,
array(
@ -81,8 +92,13 @@ final class PhragmentPatchController extends PhragmentController {
'mime-type' => 'text/plain',
'ttl' => time() + 60 * 60 * 24,
));
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$result->attachToObject($viewer, $version_b->getFragmentPHID());
unset($unguarded);
return id(new AphrontRedirectResponse())
->setURI($result->getBestURI());
->setURI($result->getDownloadURI($return));
}
}

View file

@ -0,0 +1,110 @@
<?php
final class PhragmentPolicyController extends PhragmentController {
private $dblob;
public function willProcessRequest(array $data) {
$this->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');
$fragment->setViewPolicy($v_view_policy);
$fragment->setEditPolicy($v_edit_policy);
$fragment->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();
}
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.')))
->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,
$this->renderConfigurationWarningIfRequired(),
$box),
array(
'title' => pht('Edit Fragment Policies'),
'device' => true));
}
}

View file

@ -21,6 +21,11 @@ final class PhragmentSnapshotCreateController extends PhragmentController {
return new Aphront404Response();
}
PhabricatorPolicyFilter::requireCapability(
$viewer,
$fragment,
PhabricatorPolicyCapability::CAN_EDIT);
$children = id(new PhragmentFragmentQuery())
->setViewer($viewer)
->needLatestVersion(true)
@ -161,6 +166,7 @@ final class PhragmentSnapshotCreateController extends PhragmentController {
return $this->buildApplicationPage(
array(
$crumbs,
$this->renderConfigurationWarningIfRequired(),
$box),
array(
'title' => pht('Create Fragment'),

View file

@ -14,6 +14,9 @@ final class PhragmentSnapshotDeleteController extends PhragmentController {
$snapshot = id(new PhragmentSnapshotQuery())
->setViewer($viewer)
->requireCapabilities(array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT))
->withIDs(array($this->id))
->executeOne();
if ($snapshot === null) {

View file

@ -23,6 +23,9 @@ final class PhragmentSnapshotPromoteController extends PhragmentController {
if ($this->dblob !== null) {
$this->targetFragment = id(new PhragmentFragmentQuery())
->setViewer($viewer)
->requireCapabilities(array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT))
->withPaths(array($this->dblob))
->executeOne();
if ($this->targetFragment === null) {
@ -40,6 +43,9 @@ final class PhragmentSnapshotPromoteController extends PhragmentController {
if ($this->id !== null) {
$this->targetSnapshot = id(new PhragmentSnapshotQuery())
->setViewer($viewer)
->requireCapabilities(array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT))
->withIDs(array($this->id))
->executeOne();
if ($this->targetSnapshot === null) {
@ -141,7 +147,13 @@ final class PhragmentSnapshotPromoteController extends PhragmentController {
}
$snapshot->saveTransaction();
return id(new AphrontRedirectResponse());
if ($this->id === null) {
return id(new AphrontRedirectResponse())
->setURI($this->targetFragment->getURI());
} else {
return id(new AphrontRedirectResponse())
->setURI($this->targetSnapshot->getURI());
}
}
return $this->createDialog();

View file

@ -4,6 +4,10 @@ final class PhragmentSnapshotViewController extends PhragmentController {
private $id;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->id = idx($data, "id", "");
}
@ -72,6 +76,7 @@ final class PhragmentSnapshotViewController extends PhragmentController {
return $this->buildApplicationPage(
array(
$crumbs,
$this->renderConfigurationWarningIfRequired(),
$box,
$list),
array(
@ -100,6 +105,11 @@ final class PhragmentSnapshotViewController extends PhragmentController {
"zip@".$snapshot->getName().
"/".$snapshot->getPrimaryFragment()->getPath());
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$snapshot,
PhabricatorPolicyCapability::CAN_EDIT);
$actions = id(new PhabricatorActionListView())
->setUser($viewer)
->setObject($snapshot)
@ -107,24 +117,24 @@ final class PhragmentSnapshotViewController extends PhragmentController {
$actions->addAction(
id(new PhabricatorActionView())
->setName(pht('Download Snapshot as ZIP'))
->setHref($zip_uri)
->setDisabled(false) // TODO: Policy
->setHref($this->isCorrectlyConfigured() ? $zip_uri : null)
->setDisabled(!$this->isCorrectlyConfigured())
->setIcon('zip'));
$actions->addAction(
id(new PhabricatorActionView())
->setName(pht('Delete Snapshot'))
->setHref($this->getApplicationURI(
"snapshot/delete/".$snapshot->getID()."/"))
->setDisabled(!$can_edit)
->setWorkflow(true)
->setDisabled(false) // TODO: Policy
->setIcon('delete'));
$actions->addAction(
id(new PhabricatorActionView())
->setName(pht('Promote Another Snapshot to Here'))
->setHref($this->getApplicationURI(
"snapshot/promote/".$snapshot->getID()."/"))
->setDisabled(!$can_edit)
->setWorkflow(true)
->setDisabled(false) // TODO: Policy
->setIcon('promote'));
$properties = id(new PHUIPropertyListView())

View file

@ -74,6 +74,7 @@ final class PhragmentUpdateController extends PhragmentController {
return $this->buildApplicationPage(
array(
$crumbs,
$this->renderConfigurationWarningIfRequired(),
$box),
array(
'title' => pht('Update Fragment'),

View file

@ -4,6 +4,10 @@ final class PhragmentVersionController extends PhragmentController {
private $id;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->id = idx($data, "id", 0);
}
@ -41,7 +45,7 @@ final class PhragmentVersionController extends PhragmentController {
->withPHIDs(array($version->getFilePHID()))
->executeOne();
if ($file !== null) {
$file_uri = $file->getBestURI();
$file_uri = $file->getDownloadURI();
}
$header = id(new PHUIHeaderView())
@ -59,8 +63,8 @@ final class PhragmentVersionController extends PhragmentController {
$actions->addAction(
id(new PhabricatorActionView())
->setName(pht('Download Version'))
->setHref($file_uri)
->setDisabled($file === null)
->setDisabled($file === null || !$this->isCorrectlyConfigured())
->setHref($this->isCorrectlyConfigured() ? $file_uri : null)
->setIcon('download'));
$properties = id(new PHUIPropertyListView())
@ -78,6 +82,7 @@ final class PhragmentVersionController extends PhragmentController {
return $this->buildApplicationPage(
array(
$crumbs,
$this->renderConfigurationWarningIfRequired(),
$box,
$this->renderPatchFromPreviousVersion($version, $file),
$this->renderPreviousVersionList($version)),
@ -155,11 +160,13 @@ final class PhragmentVersionController extends PhragmentController {
$item->addAttribute(phabricator_datetime(
$previous_version->getDateCreated(),
$viewer));
$patch_uri = $this->getApplicationURI(
'patch/'.$previous_version->getID().'/'.$version->getID());
$item->addAction(id(new PHUIListItemView())
->setIcon('patch')
->setName(pht("Get Patch"))
->setHref($this->getApplicationURI(
'patch/'.$previous_version->getID().'/'.$version->getID())));
->setHref($this->isCorrectlyConfigured() ? $patch_uri : null)
->setDisabled(!$this->isCorrectlyConfigured()));
$list->addItem($item);
}

View file

@ -7,6 +7,10 @@ final class PhragmentZIPController extends PhragmentController {
private $snapshotCache;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->dblob = idx($data, "dblob", "");
$this->snapshot = idx($data, "snapshot", null);
@ -87,8 +91,10 @@ final class PhragmentZIPController extends PhragmentController {
}
foreach ($mappings as $path => $file) {
if ($file !== null) {
$zip->addFromString($path, $file->loadFileData());
}
}
$zip->close();
$zip_name = $fragment->getName();
@ -103,8 +109,18 @@ final class PhragmentZIPController extends PhragmentController {
'name' => $zip_name,
'ttl' => time() + 60 * 60 * 24,
));
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$file->attachToObject($viewer, $fragment->getPHID());
unset($unguarded);
$return = $fragment->getURI();
if ($request->getExists('return')) {
$return = $request->getStr('return');
}
return id(new AphrontRedirectResponse())
->setURI($file->getBestURI());
->setURI($file->getDownloadURI($return));
}
/**

View file

@ -118,6 +118,8 @@ final class PhragmentFragment extends PhragmentDAO
$this->setLatestVersionPHID($version->getPHID());
$this->save();
$this->saveTransaction();
$file->attachToObject($viewer, $version->getPHID());
}
/**

View file

@ -48,9 +48,7 @@ final class PhragmentSnapshot extends PhragmentDAO
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW
);
return $this->getPrimaryFragment()->getCapabilities();
}
public function getPolicy($capability) {

View file

@ -1828,6 +1828,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'sql',
'name' => $this->getPatchPath('20131208.phragmentsnapshot.sql'),
),
'20131211.phragmentedges.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20131211.phragmentedges.sql'),
),
);
}
}

View file

@ -40,7 +40,7 @@ final class PhabricatorActionView extends AphrontView {
* viewing.
*/
public function getHref() {
if ($this->workflow || $this->renderAsForm) {
if (($this->workflow || $this->renderAsForm) && !$this->download) {
if (!$this->user || !$this->user->isLoggedIn()) {
return id(new PhutilURI('/auth/start/'))
->setQueryParam('next', (string)$this->getObjectURI());