From 86ec4d6021374b4384c58079131f439a0262eb9d Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Fri, 13 Dec 2013 14:42:12 +1100 Subject: [PATCH] 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 --- .../sql/patches/20131211.phragmentedges.sql | 15 +++ src/__phutil_library_map__.php | 4 + .../PhabricatorApplicationFiles.php | 1 + .../PhabricatorFileDataController.php | 12 +- .../PhabricatorApplicationPhragment.php | 8 ++ .../PhragmentCapabilityCanCreate.php | 20 ++++ .../controller/PhragmentBrowseController.php | 18 ++- .../controller/PhragmentController.php | 64 ++++++++-- .../controller/PhragmentCreateController.php | 1 + .../controller/PhragmentHistoryController.php | 33 ++++-- .../controller/PhragmentPatchController.php | 20 +++- .../controller/PhragmentPolicyController.php | 110 ++++++++++++++++++ .../PhragmentSnapshotCreateController.php | 6 + .../PhragmentSnapshotDeleteController.php | 3 + .../PhragmentSnapshotPromoteController.php | 14 ++- .../PhragmentSnapshotViewController.php | 18 ++- .../controller/PhragmentUpdateController.php | 1 + .../controller/PhragmentVersionController.php | 17 ++- .../controller/PhragmentZIPController.php | 20 +++- .../phragment/storage/PhragmentFragment.php | 2 + .../phragment/storage/PhragmentSnapshot.php | 4 +- .../patch/PhabricatorBuiltinPatchList.php | 4 + src/view/layout/PhabricatorActionView.php | 2 +- 23 files changed, 350 insertions(+), 47 deletions(-) create mode 100644 resources/sql/patches/20131211.phragmentedges.sql create mode 100644 src/applications/phragment/capability/PhragmentCapabilityCanCreate.php create mode 100644 src/applications/phragment/controller/PhragmentPolicyController.php diff --git a/resources/sql/patches/20131211.phragmentedges.sql b/resources/sql/patches/20131211.phragmentedges.sql new file mode 100644 index 0000000000..51eead5c5d --- /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/__phutil_library_map__.php b/src/__phutil_library_map__.php index 2c3d402026..3d9bdd11c2 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -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( diff --git a/src/applications/files/application/PhabricatorApplicationFiles.php b/src/applications/files/application/PhabricatorApplicationFiles.php index cac7c4435f..ecb9968288 100644 --- a/src/applications/files/application/PhabricatorApplicationFiles.php +++ b/src/applications/files/application/PhabricatorApplicationFiles.php @@ -61,6 +61,7 @@ final class PhabricatorApplicationFiles extends PhabricatorApplication { 'xform/(?P[^/]+)/(?P[^/]+)/(?P[^/]+)/' => 'PhabricatorFileTransformController', 'uploaddialog/' => 'PhabricatorFileUploadDialogController', + 'download/(?P[^/]+)/' => 'PhabricatorFileDialogController', ), ); } diff --git a/src/applications/files/controller/PhabricatorFileDataController.php b/src/applications/files/controller/PhabricatorFileDataController.php index 56ab38fd6b..3f82099606 100644 --- a/src/applications/files/controller/PhabricatorFileDataController.php +++ b/src/applications/files/controller/PhabricatorFileDataController.php @@ -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())); } diff --git a/src/applications/phragment/application/PhabricatorApplicationPhragment.php b/src/applications/phragment/application/PhabricatorApplicationPhragment.php index d6036fbda3..aaa8f3c4bc 100644 --- a/src/applications/phragment/application/PhabricatorApplicationPhragment.php +++ b/src/applications/phragment/application/PhabricatorApplicationPhragment.php @@ -37,6 +37,7 @@ final class PhabricatorApplicationPhragment extends PhabricatorApplication { '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 @@ final class PhabricatorApplicationPhragment extends PhabricatorApplication { ); } + 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 index 0000000000..860ece8f25 --- /dev/null +++ b/src/applications/phragment/capability/PhragmentCapabilityCanCreate.php @@ -0,0 +1,20 @@ +dblob = idx($data, "dblob", ""); } @@ -24,11 +28,14 @@ final class PhragmentBrowseController extends PhragmentController { } $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 @@ final class PhragmentBrowseController extends PhragmentController { 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 index b60d509df4..200772377d 100644 --- a/src/applications/phragment/controller/PhragmentController.php +++ b/src/applications/phragment/controller/PhragmentController.php @@ -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; + } + } diff --git a/src/applications/phragment/controller/PhragmentCreateController.php b/src/applications/phragment/controller/PhragmentCreateController.php index 8ae0ef1ccb..3927fb203e 100644 --- a/src/applications/phragment/controller/PhragmentCreateController.php +++ b/src/applications/phragment/controller/PhragmentCreateController.php @@ -123,6 +123,7 @@ final class PhragmentCreateController extends PhragmentController { 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 index 893c284c2c..63a731c6a1 100644 --- a/src/applications/phragment/controller/PhragmentHistoryController.php +++ b/src/applications/phragment/controller/PhragmentHistoryController.php @@ -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); - $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 @@ 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( diff --git a/src/applications/phragment/controller/PhragmentPatchController.php b/src/applications/phragment/controller/PhragmentPatchController.php index 41bc2de963..b3f6b6bfa7 100644 --- a/src/applications/phragment/controller/PhragmentPatchController.php +++ b/src/applications/phragment/controller/PhragmentPatchController.php @@ -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)); } } diff --git a/src/applications/phragment/controller/PhragmentPolicyController.php b/src/applications/phragment/controller/PhragmentPolicyController.php new file mode 100644 index 0000000000..e4ef33c212 --- /dev/null +++ b/src/applications/phragment/controller/PhragmentPolicyController.php @@ -0,0 +1,110 @@ +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 index d1f7e2eb98..923045ce5f 100644 --- a/src/applications/phragment/controller/PhragmentSnapshotCreateController.php +++ b/src/applications/phragment/controller/PhragmentSnapshotCreateController.php @@ -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'), diff --git a/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php b/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php index 851da9e970..902e51e1d0 100644 --- a/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php +++ b/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php @@ -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) { diff --git a/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php b/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php index 7b449eef27..9876c69e35 100644 --- a/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php +++ b/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php @@ -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(); diff --git a/src/applications/phragment/controller/PhragmentSnapshotViewController.php b/src/applications/phragment/controller/PhragmentSnapshotViewController.php index e6272ffc79..4cf0b577ec 100644 --- a/src/applications/phragment/controller/PhragmentSnapshotViewController.php +++ b/src/applications/phragment/controller/PhragmentSnapshotViewController.php @@ -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()) diff --git a/src/applications/phragment/controller/PhragmentUpdateController.php b/src/applications/phragment/controller/PhragmentUpdateController.php index ea9f3b6c59..1c233bb74e 100644 --- a/src/applications/phragment/controller/PhragmentUpdateController.php +++ b/src/applications/phragment/controller/PhragmentUpdateController.php @@ -74,6 +74,7 @@ final class PhragmentUpdateController extends PhragmentController { 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 index 0579e96e4c..a0e849c03f 100644 --- a/src/applications/phragment/controller/PhragmentVersionController.php +++ b/src/applications/phragment/controller/PhragmentVersionController.php @@ -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); } diff --git a/src/applications/phragment/controller/PhragmentZIPController.php b/src/applications/phragment/controller/PhragmentZIPController.php index 183de2d247..59fc37c831 100644 --- a/src/applications/phragment/controller/PhragmentZIPController.php +++ b/src/applications/phragment/controller/PhragmentZIPController.php @@ -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,7 +91,9 @@ final class PhragmentZIPController extends PhragmentController { } foreach ($mappings as $path => $file) { - $zip->addFromString($path, $file->loadFileData()); + if ($file !== null) { + $zip->addFromString($path, $file->loadFileData()); + } } $zip->close(); @@ -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)); } /** diff --git a/src/applications/phragment/storage/PhragmentFragment.php b/src/applications/phragment/storage/PhragmentFragment.php index 07b972def7..c18ef3ff84 100644 --- a/src/applications/phragment/storage/PhragmentFragment.php +++ b/src/applications/phragment/storage/PhragmentFragment.php @@ -118,6 +118,8 @@ final class PhragmentFragment extends PhragmentDAO $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 index ea0eecf9c2..59d0a04b26 100644 --- a/src/applications/phragment/storage/PhragmentSnapshot.php +++ b/src/applications/phragment/storage/PhragmentSnapshot.php @@ -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) { diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php index 35d4846370..9d7e85c4e2 100644 --- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -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'), + ), ); } } diff --git a/src/view/layout/PhabricatorActionView.php b/src/view/layout/PhabricatorActionView.php index 09da51c0ba..cefe9df8df 100644 --- a/src/view/layout/PhabricatorActionView.php +++ b/src/view/layout/PhabricatorActionView.php @@ -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());