Page MenuHomePhabricator

D7751.id17558.diff

D7751.id17558.diff

diff --git a/resources/sql/patches/20131211.phragmentedges.sql b/resources/sql/patches/20131211.phragmentedges.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/patches/20131211.phragmentedges.sql
@@ -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;
diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php
--- a/src/__celerity_resource_map__.php
+++ b/src/__celerity_resource_map__.php
@@ -1776,6 +1776,18 @@
),
'disk' => '/rsrc/js/core/behavior-fancy-datepicker.js',
),
+ 'javelin-behavior-files-download-redirect' =>
+ array(
+ 'uri' => '/res/e3513aad/rsrc/js/application/files/behavior-files-download-redirect.js',
+ 'type' => 'js',
+ 'requires' =>
+ array(
+ 0 => 'javelin-behavior',
+ 1 => 'javelin-dom',
+ 2 => 'javelin-workflow',
+ ),
+ 'disk' => '/rsrc/js/application/files/behavior-files-download-redirect.js',
+ ),
'javelin-behavior-global-drag-and-drop' =>
array(
'uri' => '/res/ee8e9c39/rsrc/js/core/behavior-global-drag-and-drop.js',
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2176,6 +2176,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 +2190,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',
@@ -4782,6 +4784,7 @@
'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider',
'PhortuneWePayPaymentProvider' => 'PhortunePaymentProvider',
'PhragmentBrowseController' => 'PhragmentController',
+ 'PhragmentCapabilityCanCreate' => 'PhabricatorPolicyCapability',
'PhragmentController' => 'PhabricatorController',
'PhragmentCreateController' => 'PhragmentController',
'PhragmentDAO' => 'PhabricatorLiskDAO',
@@ -4803,6 +4806,7 @@
'PhragmentPHIDTypeSnapshot' => 'PhabricatorPHIDType',
'PhragmentPatchController' => 'PhragmentController',
'PhragmentPatchUtil' => 'Phobject',
+ 'PhragmentPolicyController' => 'PhragmentController',
'PhragmentRevertController' => 'PhragmentController',
'PhragmentSnapshot' =>
array(
diff --git a/src/applications/files/application/PhabricatorApplicationFiles.php b/src/applications/files/application/PhabricatorApplicationFiles.php
--- a/src/applications/files/application/PhabricatorApplicationFiles.php
+++ b/src/applications/files/application/PhabricatorApplicationFiles.php
@@ -61,6 +61,7 @@
'xform/(?P<transform>[^/]+)/(?P<phid>[^/]+)/(?P<key>[^/]+)/'
=> 'PhabricatorFileTransformController',
'uploaddialog/' => 'PhabricatorFileUploadDialogController',
+ 'download/(?P<phid>[^/]+)/' => 'PhabricatorFileDialogController',
),
);
}
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
@@ -66,12 +66,12 @@
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()));
}
diff --git a/src/applications/phragment/application/PhabricatorApplicationPhragment.php b/src/applications/phragment/application/PhabricatorApplicationPhragment.php
--- a/src/applications/phragment/application/PhabricatorApplicationPhragment.php
+++ b/src/applications/phragment/application/PhabricatorApplicationPhragment.php
@@ -37,6 +37,7 @@
'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 @@
);
}
+ protected function getCustomCapabilities() {
+ return array(
+ PhragmentCapabilityCanCreate::CAPABILITY => array(
+ ),
+ );
+ }
+
}
diff --git a/src/applications/phragment/capability/PhragmentCapabilityCanCreate.php b/src/applications/phragment/capability/PhragmentCapabilityCanCreate.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phragment/capability/PhragmentCapabilityCanCreate.php
@@ -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.');
+ }
+
+}
diff --git a/src/applications/phragment/controller/PhragmentBrowseController.php b/src/applications/phragment/controller/PhragmentBrowseController.php
--- a/src/applications/phragment/controller/PhragmentBrowseController.php
+++ b/src/applications/phragment/controller/PhragmentBrowseController.php
@@ -4,6 +4,10 @@
private $dblob;
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function willProcessRequest(array $data) {
$this->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);
@@ -79,6 +86,7 @@
return $this->buildApplicationPage(
array(
$crumbs,
+ $this->renderConfigurationWarningIfRequired(),
$current_box,
$list),
array(
diff --git a/src/applications/phragment/controller/PhragmentController.php b/src/applications/phragment/controller/PhragmentController.php
--- a/src/applications/phragment/controller/PhragmentController.php
+++ b/src/applications/phragment/controller/PhragmentController.php
@@ -84,46 +84,62 @@
->withPHIDs(array($fragment->getLatestVersion()->getFilePHID()))
->executeOne();
if ($file !== null) {
- $file_uri = $file->getBestURI();
+ $file_uri = $file->getDownloadURI();
}
}
$header = id(new PHUIHeaderView())
->setHeader($fragment->getName())
->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)
->setObjectURI($fragment->getURI());
$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,15 +158,16 @@
->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())
->setName(pht('Promote Snapshot to Here'))
->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 @@
->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;
+ }
+
}
diff --git a/src/applications/phragment/controller/PhragmentCreateController.php b/src/applications/phragment/controller/PhragmentCreateController.php
--- a/src/applications/phragment/controller/PhragmentCreateController.php
+++ b/src/applications/phragment/controller/PhragmentCreateController.php
@@ -123,6 +123,7 @@
return $this->buildApplicationPage(
array(
$crumbs,
+ $this->renderConfigurationWarningIfRequired(),
$box),
array(
'title' => pht('Create Fragment'),
diff --git a/src/applications/phragment/controller/PhragmentHistoryController.php b/src/applications/phragment/controller/PhragmentHistoryController.php
--- a/src/applications/phragment/controller/PhragmentHistoryController.php
+++ b/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)
@@ -71,21 +83,24 @@
$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;
}
return $this->buildApplicationPage(
array(
$crumbs,
+ $this->renderConfigurationWarningIfRequired(),
$current_box,
$list),
array(
diff --git a/src/applications/phragment/controller/PhragmentPatchController.php b/src/applications/phragment/controller/PhragmentPatchController.php
--- a/src/applications/phragment/controller/PhragmentPatchController.php
+++ b/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';
@@ -74,15 +80,25 @@
$a_sequence.'.'.
$version_b->getSequence().'.patch';
+ $return = $version_b->getURI();
+ if ($request->getExists('return')) {
+ $return = $request->getStr('return');
+ }
+
$result = PhabricatorFile::buildFromFileDataOrHash(
$patch,
array(
'name' => $name,
'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));
}
}
diff --git a/src/applications/phragment/controller/PhragmentPolicyController.php b/src/applications/phragment/controller/PhragmentPolicyController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phragment/controller/PhragmentPolicyController.php
@@ -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));
+ }
+
+}
diff --git a/src/applications/phragment/controller/PhragmentSnapshotCreateController.php b/src/applications/phragment/controller/PhragmentSnapshotCreateController.php
--- a/src/applications/phragment/controller/PhragmentSnapshotCreateController.php
+++ b/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)
@@ -161,6 +166,7 @@
return $this->buildApplicationPage(
array(
$crumbs,
+ $this->renderConfigurationWarningIfRequired(),
$box),
array(
'title' => pht('Create Fragment'),
diff --git a/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php b/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php
--- a/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php
+++ b/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php
@@ -14,6 +14,9 @@
$snapshot = id(new PhragmentSnapshotQuery())
->setViewer($viewer)
+ ->requireCapabilities(array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT))
->withIDs(array($this->id))
->executeOne();
if ($snapshot === null) {
diff --git a/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php b/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php
--- a/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php
+++ b/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php
@@ -23,6 +23,9 @@
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 @@
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 @@
}
$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();
diff --git a/src/applications/phragment/controller/PhragmentSnapshotViewController.php b/src/applications/phragment/controller/PhragmentSnapshotViewController.php
--- a/src/applications/phragment/controller/PhragmentSnapshotViewController.php
+++ b/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", "");
}
@@ -72,6 +76,7 @@
return $this->buildApplicationPage(
array(
$crumbs,
+ $this->renderConfigurationWarningIfRequired(),
$box,
$list),
array(
@@ -100,31 +105,36 @@
"zip@".$snapshot->getName().
"/".$snapshot->getPrimaryFragment()->getPath());
+ $can_edit = PhabricatorPolicyFilter::hasCapability(
+ $viewer,
+ $snapshot,
+ PhabricatorPolicyCapability::CAN_EDIT);
+
$actions = id(new PhabricatorActionListView())
->setUser($viewer)
->setObject($snapshot)
->setObjectURI($snapshot->getURI());
$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())
diff --git a/src/applications/phragment/controller/PhragmentUpdateController.php b/src/applications/phragment/controller/PhragmentUpdateController.php
--- a/src/applications/phragment/controller/PhragmentUpdateController.php
+++ b/src/applications/phragment/controller/PhragmentUpdateController.php
@@ -74,6 +74,7 @@
return $this->buildApplicationPage(
array(
$crumbs,
+ $this->renderConfigurationWarningIfRequired(),
$box),
array(
'title' => pht('Update Fragment'),
diff --git a/src/applications/phragment/controller/PhragmentVersionController.php b/src/applications/phragment/controller/PhragmentVersionController.php
--- a/src/applications/phragment/controller/PhragmentVersionController.php
+++ b/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())
@@ -59,8 +63,8 @@
$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 @@
return $this->buildApplicationPage(
array(
$crumbs,
+ $this->renderConfigurationWarningIfRequired(),
$box,
$this->renderPatchFromPreviousVersion($version, $file),
$this->renderPreviousVersionList($version)),
@@ -155,11 +160,13 @@
$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);
}
diff --git a/src/applications/phragment/controller/PhragmentZIPController.php b/src/applications/phragment/controller/PhragmentZIPController.php
--- a/src/applications/phragment/controller/PhragmentZIPController.php
+++ b/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();
@@ -103,8 +109,18 @@
'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));
}
/**
diff --git a/src/applications/phragment/storage/PhragmentFragment.php b/src/applications/phragment/storage/PhragmentFragment.php
--- a/src/applications/phragment/storage/PhragmentFragment.php
+++ b/src/applications/phragment/storage/PhragmentFragment.php
@@ -118,6 +118,8 @@
$this->setLatestVersionPHID($version->getPHID());
$this->save();
$this->saveTransaction();
+
+ $file->attachToObject($viewer, $version->getPHID());
}
/**
diff --git a/src/applications/phragment/storage/PhragmentSnapshot.php b/src/applications/phragment/storage/PhragmentSnapshot.php
--- a/src/applications/phragment/storage/PhragmentSnapshot.php
+++ b/src/applications/phragment/storage/PhragmentSnapshot.php
@@ -48,9 +48,7 @@
public function getCapabilities() {
- return array(
- PhabricatorPolicyCapability::CAN_VIEW
- );
+ return $this->getPrimaryFragment()->getCapabilities();
}
public function getPolicy($capability) {
diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
--- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
+++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
@@ -1828,6 +1828,10 @@
'type' => 'sql',
'name' => $this->getPatchPath('20131208.phragmentsnapshot.sql'),
),
+ '20131211.phragmentedges.sql' => array(
+ 'type' => 'sql',
+ 'name' => $this->getPatchPath('20131211.phragmentedges.sql'),
+ ),
);
}
}
diff --git a/src/view/layout/PhabricatorActionView.php b/src/view/layout/PhabricatorActionView.php
--- a/src/view/layout/PhabricatorActionView.php
+++ b/src/view/layout/PhabricatorActionView.php
@@ -40,7 +40,7 @@
* 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());
diff --git a/webroot/rsrc/js/application/files/behavior-files-download-redirect.js b/webroot/rsrc/js/application/files/behavior-files-download-redirect.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/application/files/behavior-files-download-redirect.js
@@ -0,0 +1,19 @@
+/**
+ * @provides javelin-behavior-files-download-redirect
+ * @requires javelin-behavior
+ * javelin-dom
+ * javelin-workflow
+ */
+
+JX.behavior('files-download-redirect', function(config) {
+
+ JX.DOM.listen(
+ JX.$(config.dialogID),
+ 'submit',
+ null,
+ function(e) {
+ window.setTimeout(function() { location.href = config.return; }, 2000);
+ });
+
+});
+

File Metadata

Mime Type
text/x-diff
Storage Engine
amazon-s3
Storage Format
Raw Data
Storage Handle
phabricator/z3/3y/sanbltesi6vhkif3
Default Alt Text
D7751.id17558.diff (34 KB)

Event Timeline