1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-02-27 14:09:11 +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', 'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php',
'PhortuneWePayPaymentProvider' => 'applications/phortune/provider/PhortuneWePayPaymentProvider.php', 'PhortuneWePayPaymentProvider' => 'applications/phortune/provider/PhortuneWePayPaymentProvider.php',
'PhragmentBrowseController' => 'applications/phragment/controller/PhragmentBrowseController.php', 'PhragmentBrowseController' => 'applications/phragment/controller/PhragmentBrowseController.php',
'PhragmentCapabilityCanCreate' => 'applications/phragment/capability/PhragmentCapabilityCanCreate.php',
'PhragmentController' => 'applications/phragment/controller/PhragmentController.php', 'PhragmentController' => 'applications/phragment/controller/PhragmentController.php',
'PhragmentCreateController' => 'applications/phragment/controller/PhragmentCreateController.php', 'PhragmentCreateController' => 'applications/phragment/controller/PhragmentCreateController.php',
'PhragmentDAO' => 'applications/phragment/storage/PhragmentDAO.php', 'PhragmentDAO' => 'applications/phragment/storage/PhragmentDAO.php',
@ -2193,6 +2194,7 @@ phutil_register_library_map(array(
'PhragmentPHIDTypeSnapshot' => 'applications/phragment/phid/PhragmentPHIDTypeSnapshot.php', 'PhragmentPHIDTypeSnapshot' => 'applications/phragment/phid/PhragmentPHIDTypeSnapshot.php',
'PhragmentPatchController' => 'applications/phragment/controller/PhragmentPatchController.php', 'PhragmentPatchController' => 'applications/phragment/controller/PhragmentPatchController.php',
'PhragmentPatchUtil' => 'applications/phragment/util/PhragmentPatchUtil.php', 'PhragmentPatchUtil' => 'applications/phragment/util/PhragmentPatchUtil.php',
'PhragmentPolicyController' => 'applications/phragment/controller/PhragmentPolicyController.php',
'PhragmentRevertController' => 'applications/phragment/controller/PhragmentRevertController.php', 'PhragmentRevertController' => 'applications/phragment/controller/PhragmentRevertController.php',
'PhragmentSnapshot' => 'applications/phragment/storage/PhragmentSnapshot.php', 'PhragmentSnapshot' => 'applications/phragment/storage/PhragmentSnapshot.php',
'PhragmentSnapshotChild' => 'applications/phragment/storage/PhragmentSnapshotChild.php', 'PhragmentSnapshotChild' => 'applications/phragment/storage/PhragmentSnapshotChild.php',
@ -4790,6 +4792,7 @@ phutil_register_library_map(array(
'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider', 'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider',
'PhortuneWePayPaymentProvider' => 'PhortunePaymentProvider', 'PhortuneWePayPaymentProvider' => 'PhortunePaymentProvider',
'PhragmentBrowseController' => 'PhragmentController', 'PhragmentBrowseController' => 'PhragmentController',
'PhragmentCapabilityCanCreate' => 'PhabricatorPolicyCapability',
'PhragmentController' => 'PhabricatorController', 'PhragmentController' => 'PhabricatorController',
'PhragmentCreateController' => 'PhragmentController', 'PhragmentCreateController' => 'PhragmentController',
'PhragmentDAO' => 'PhabricatorLiskDAO', 'PhragmentDAO' => 'PhabricatorLiskDAO',
@ -4811,6 +4814,7 @@ phutil_register_library_map(array(
'PhragmentPHIDTypeSnapshot' => 'PhabricatorPHIDType', 'PhragmentPHIDTypeSnapshot' => 'PhabricatorPHIDType',
'PhragmentPatchController' => 'PhragmentController', 'PhragmentPatchController' => 'PhragmentController',
'PhragmentPatchUtil' => 'Phobject', 'PhragmentPatchUtil' => 'Phobject',
'PhragmentPolicyController' => 'PhragmentController',
'PhragmentRevertController' => 'PhragmentController', 'PhragmentRevertController' => 'PhragmentController',
'PhragmentSnapshot' => 'PhragmentSnapshot' =>
array( array(

View file

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

View file

@ -66,12 +66,12 @@ final class PhabricatorFileDataController extends PhabricatorFileController {
if ($is_viewable && !$force_download) { if ($is_viewable && !$force_download) {
$response->setMimeType($file->getViewableMimeType()); $response->setMimeType($file->getViewableMimeType());
} else { } else {
if (!$request->isHTTPPost()) { if (!$request->isHTTPPost() && !$alt_domain) {
// NOTE: Require POST to download files. We'd rather go full-bore and // NOTE: Require POST to download files from the primary domain. We'd
// do a real CSRF check, but can't currently authenticate users on the // rather go full-bore and do a real CSRF check, but can't currently
// file domain. This should blunt any attacks based on iframes, script // authenticate users on the file domain. This should blunt any
// tags, applet tags, etc., at least. Send the user to the "info" page // attacks based on iframes, script tags, applet tags, etc., at least.
// if they're using some other method. // Send the user to the "info" page if they're using some other method.
return id(new AphrontRedirectResponse()) return id(new AphrontRedirectResponse())
->setURI(PhabricatorEnv::getProductionURI($file->getBestURI())); ->setURI(PhabricatorEnv::getProductionURI($file->getBestURI()));
} }

View file

@ -37,6 +37,7 @@ final class PhabricatorApplicationPhragment extends PhabricatorApplication {
'browse/(?P<dblob>.*)' => 'PhragmentBrowseController', 'browse/(?P<dblob>.*)' => 'PhragmentBrowseController',
'create/(?P<dblob>.*)' => 'PhragmentCreateController', 'create/(?P<dblob>.*)' => 'PhragmentCreateController',
'update/(?P<dblob>.*)' => 'PhragmentUpdateController', 'update/(?P<dblob>.*)' => 'PhragmentUpdateController',
'policy/(?P<dblob>.*)' => 'PhragmentPolicyController',
'history/(?P<dblob>.*)' => 'PhragmentHistoryController', 'history/(?P<dblob>.*)' => 'PhragmentHistoryController',
'zip/(?P<dblob>.*)' => 'PhragmentZIPController', 'zip/(?P<dblob>.*)' => 'PhragmentZIPController',
'zip@(?P<snapshot>[^/]+)/(?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; private $dblob;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) { public function willProcessRequest(array $data) {
$this->dblob = idx($data, "dblob", ""); $this->dblob = idx($data, "dblob", "");
} }
@ -24,11 +28,14 @@ final class PhragmentBrowseController extends PhragmentController {
} }
$crumbs = $this->buildApplicationCrumbsWithPath($parents); $crumbs = $this->buildApplicationCrumbsWithPath($parents);
if ($this->hasApplicationCapability(
PhragmentCapabilityCanCreate::CAPABILITY)) {
$crumbs->addAction( $crumbs->addAction(
id(new PHUIListItemView()) id(new PHUIListItemView())
->setName(pht('Create Fragment')) ->setName(pht('Create Fragment'))
->setHref($this->getApplicationURI('/create/'.$path)) ->setHref($this->getApplicationURI('/create/'.$path))
->setIcon('create')); ->setIcon('create'));
}
$current_box = $this->createCurrentFragmentView($current, false); $current_box = $this->createCurrentFragmentView($current, false);
@ -79,6 +86,7 @@ final class PhragmentBrowseController extends PhragmentController {
return $this->buildApplicationPage( return $this->buildApplicationPage(
array( array(
$crumbs, $crumbs,
$this->renderConfigurationWarningIfRequired(),
$current_box, $current_box,
$list), $list),
array( array(

View file

@ -84,7 +84,7 @@ abstract class PhragmentController extends PhabricatorController {
->withPHIDs(array($fragment->getLatestVersion()->getFilePHID())) ->withPHIDs(array($fragment->getLatestVersion()->getFilePHID()))
->executeOne(); ->executeOne();
if ($file !== null) { if ($file !== null) {
$file_uri = $file->getBestURI(); $file_uri = $file->getDownloadURI();
} }
} }
@ -93,6 +93,13 @@ abstract class PhragmentController extends PhabricatorController {
->setPolicyObject($fragment) ->setPolicyObject($fragment)
->setUser($viewer); ->setUser($viewer);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$fragment,
PhabricatorPolicyCapability::CAN_EDIT);
$zip_uri = $this->getApplicationURI("zip/".$fragment->getPath());
$actions = id(new PhabricatorActionListView()) $actions = id(new PhabricatorActionListView())
->setUser($viewer) ->setUser($viewer)
->setObject($fragment) ->setObject($fragment)
@ -100,30 +107,39 @@ abstract class PhragmentController extends PhabricatorController {
$actions->addAction( $actions->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setName(pht('Download Fragment')) ->setName(pht('Download Fragment'))
->setHref($file_uri) ->setHref($this->isCorrectlyConfigured() ? $file_uri : null)
->setDisabled($file === null) ->setDisabled($file === null || !$this->isCorrectlyConfigured())
->setIcon('download')); ->setIcon('download'));
$actions->addAction( $actions->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setName(pht('Download Contents as ZIP')) ->setName(pht('Download Contents as ZIP'))
->setHref($this->getApplicationURI("zip/".$fragment->getPath())) ->setHref($this->isCorrectlyConfigured() ? $zip_uri : null)
->setDisabled(false) // TODO: Policy ->setDisabled(!$this->isCorrectlyConfigured())
->setIcon('zip')); ->setIcon('zip'));
if (!$fragment->isDirectory()) { if (!$fragment->isDirectory()) {
$actions->addAction( $actions->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setName(pht('Update Fragment')) ->setName(pht('Update Fragment'))
->setHref($this->getApplicationURI("update/".$fragment->getPath())) ->setHref($this->getApplicationURI("update/".$fragment->getPath()))
->setDisabled(false) // TODO: Policy ->setDisabled(!$can_edit)
->setWorkflow(!$can_edit)
->setIcon('edit')); ->setIcon('edit'));
} else { } else {
$actions->addAction( $actions->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setName(pht('Convert to File')) ->setName(pht('Convert to File'))
->setHref($this->getApplicationURI("update/".$fragment->getPath())) ->setHref($this->getApplicationURI("update/".$fragment->getPath()))
->setDisabled(false) // TODO: Policy ->setDisabled(!$can_edit)
->setWorkflow(!$can_edit)
->setIcon('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) { if ($is_history_view) {
$actions->addAction( $actions->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
@ -142,7 +158,8 @@ abstract class PhragmentController extends PhabricatorController {
->setName(pht('Create Snapshot')) ->setName(pht('Create Snapshot'))
->setHref($this->getApplicationURI( ->setHref($this->getApplicationURI(
"snapshot/create/".$fragment->getPath())) "snapshot/create/".$fragment->getPath()))
->setDisabled(false) // TODO: Policy ->setDisabled(!$can_edit)
->setWorkflow(!$can_edit)
->setIcon('snapshot')); ->setIcon('snapshot'));
$actions->addAction( $actions->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
@ -150,7 +167,7 @@ abstract class PhragmentController extends PhabricatorController {
->setHref($this->getApplicationURI( ->setHref($this->getApplicationURI(
"snapshot/promote/latest/".$fragment->getPath())) "snapshot/promote/latest/".$fragment->getPath()))
->setWorkflow(true) ->setWorkflow(true)
->setDisabled(false) // TODO: Policy ->setDisabled(!$can_edit)
->setIcon('promote')); ->setIcon('promote'));
$properties = id(new PHUIPropertyListView()) $properties = id(new PHUIPropertyListView())
@ -188,4 +205,33 @@ abstract class PhragmentController extends PhabricatorController {
->addPropertyList($properties); ->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( return $this->buildApplicationPage(
array( array(
$crumbs, $crumbs,
$this->renderConfigurationWarningIfRequired(),
$box), $box),
array( array(
'title' => pht('Create Fragment'), 'title' => pht('Create Fragment'),

View file

@ -4,6 +4,10 @@ final class PhragmentHistoryController extends PhragmentController {
private $dblob; private $dblob;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) { public function willProcessRequest(array $data) {
$this->dblob = idx($data, "dblob", ""); $this->dblob = idx($data, "dblob", "");
} }
@ -21,11 +25,14 @@ final class PhragmentHistoryController extends PhragmentController {
$path = $current->getPath(); $path = $current->getPath();
$crumbs = $this->buildApplicationCrumbsWithPath($parents); $crumbs = $this->buildApplicationCrumbsWithPath($parents);
if ($this->hasApplicationCapability(
PhragmentCapabilityCanCreate::CAPABILITY)) {
$crumbs->addAction( $crumbs->addAction(
id(new PHUIListItemView()) id(new PHUIListItemView())
->setName(pht('Create Fragment')) ->setName(pht('Create Fragment'))
->setHref($this->getApplicationURI('/create/'.$path)) ->setHref($this->getApplicationURI('/create/'.$path))
->setIcon('create')); ->setIcon('create'));
}
$current_box = $this->createCurrentFragmentView($current, true); $current_box = $this->createCurrentFragmentView($current, true);
@ -44,6 +51,11 @@ final class PhragmentHistoryController extends PhragmentController {
->execute(); ->execute();
$files = mpull($files, null, 'getPHID'); $files = mpull($files, null, 'getPHID');
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$current,
PhabricatorPolicyCapability::CAN_EDIT);
$first = true; $first = true;
foreach ($versions as $version) { foreach ($versions as $version) {
$item = id(new PHUIObjectItemView()); $item = id(new PHUIObjectItemView());
@ -58,7 +70,7 @@ final class PhragmentHistoryController extends PhragmentController {
$item->addAttribute('Deletion'); $item->addAttribute('Deletion');
} }
if (!$first) { if (!$first && $can_edit) {
$item->addAction(id(new PHUIListItemView()) $item->addAction(id(new PHUIListItemView())
->setIcon('undo') ->setIcon('undo')
->setRenderNameAsTooltip(true) ->setRenderNameAsTooltip(true)
@ -71,13 +83,15 @@ final class PhragmentHistoryController extends PhragmentController {
$disabled = !isset($files[$version->getFilePHID()]); $disabled = !isset($files[$version->getFilePHID()]);
$action = id(new PHUIListItemView()) $action = id(new PHUIListItemView())
->setIcon('download') ->setIcon('download')
->setDisabled($disabled) ->setDisabled($disabled || !$this->isCorrectlyConfigured())
->setRenderNameAsTooltip(true) ->setRenderNameAsTooltip(true)
->setName(pht("Download")); ->setName(pht("Download"));
if (!$disabled) { if (!$disabled && $this->isCorrectlyConfigured()) {
$action->setHref($files[$version->getFilePHID()]->getBestURI()); $action->setHref($files[$version->getFilePHID()]
->getDownloadURI($version->getURI()));
} }
$item->addAction($action); $item->addAction($action);
$list->addItem($item); $list->addItem($item);
$first = false; $first = false;
@ -86,6 +100,7 @@ final class PhragmentHistoryController extends PhragmentController {
return $this->buildApplicationPage( return $this->buildApplicationPage(
array( array(
$crumbs, $crumbs,
$this->renderConfigurationWarningIfRequired(),
$current_box, $current_box,
$list), $list),
array( array(

View file

@ -5,6 +5,10 @@ final class PhragmentPatchController extends PhragmentController {
private $aid; private $aid;
private $bid; private $bid;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) { public function willProcessRequest(array $data) {
$this->aid = idx($data, "aid", 0); $this->aid = idx($data, "aid", 0);
$this->bid = idx($data, "bid", 0); $this->bid = idx($data, "bid", 0);
@ -61,7 +65,9 @@ final class PhragmentPatchController extends PhragmentController {
$patch = PhragmentPatchUtil::calculatePatch($file_a, $file_b); $patch = PhragmentPatchUtil::calculatePatch($file_a, $file_b);
if ($patch === null) { 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'; $a_sequence = 'x';
@ -74,6 +80,11 @@ final class PhragmentPatchController extends PhragmentController {
$a_sequence.'.'. $a_sequence.'.'.
$version_b->getSequence().'.patch'; $version_b->getSequence().'.patch';
$return = $version_b->getURI();
if ($request->getExists('return')) {
$return = $request->getStr('return');
}
$result = PhabricatorFile::buildFromFileDataOrHash( $result = PhabricatorFile::buildFromFileDataOrHash(
$patch, $patch,
array( array(
@ -81,8 +92,13 @@ final class PhragmentPatchController extends PhragmentController {
'mime-type' => 'text/plain', 'mime-type' => 'text/plain',
'ttl' => time() + 60 * 60 * 24, 'ttl' => time() + 60 * 60 * 24,
)); ));
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$result->attachToObject($viewer, $version_b->getFragmentPHID());
unset($unguarded);
return id(new AphrontRedirectResponse()) 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(); return new Aphront404Response();
} }
PhabricatorPolicyFilter::requireCapability(
$viewer,
$fragment,
PhabricatorPolicyCapability::CAN_EDIT);
$children = id(new PhragmentFragmentQuery()) $children = id(new PhragmentFragmentQuery())
->setViewer($viewer) ->setViewer($viewer)
->needLatestVersion(true) ->needLatestVersion(true)
@ -161,6 +166,7 @@ final class PhragmentSnapshotCreateController extends PhragmentController {
return $this->buildApplicationPage( return $this->buildApplicationPage(
array( array(
$crumbs, $crumbs,
$this->renderConfigurationWarningIfRequired(),
$box), $box),
array( array(
'title' => pht('Create Fragment'), 'title' => pht('Create Fragment'),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,6 +7,10 @@ final class PhragmentZIPController extends PhragmentController {
private $snapshotCache; private $snapshotCache;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) { public function willProcessRequest(array $data) {
$this->dblob = idx($data, "dblob", ""); $this->dblob = idx($data, "dblob", "");
$this->snapshot = idx($data, "snapshot", null); $this->snapshot = idx($data, "snapshot", null);
@ -87,8 +91,10 @@ final class PhragmentZIPController extends PhragmentController {
} }
foreach ($mappings as $path => $file) { foreach ($mappings as $path => $file) {
if ($file !== null) {
$zip->addFromString($path, $file->loadFileData()); $zip->addFromString($path, $file->loadFileData());
} }
}
$zip->close(); $zip->close();
$zip_name = $fragment->getName(); $zip_name = $fragment->getName();
@ -103,8 +109,18 @@ final class PhragmentZIPController extends PhragmentController {
'name' => $zip_name, 'name' => $zip_name,
'ttl' => time() + 60 * 60 * 24, '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()) 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->setLatestVersionPHID($version->getPHID());
$this->save(); $this->save();
$this->saveTransaction(); $this->saveTransaction();
$file->attachToObject($viewer, $version->getPHID());
} }
/** /**

View file

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

View file

@ -1828,6 +1828,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'sql', 'type' => 'sql',
'name' => $this->getPatchPath('20131208.phragmentsnapshot.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. * viewing.
*/ */
public function getHref() { public function getHref() {
if ($this->workflow || $this->renderAsForm) { if (($this->workflow || $this->renderAsForm) && !$this->download) {
if (!$this->user || !$this->user->isLoggedIn()) { if (!$this->user || !$this->user->isLoggedIn()) {
return id(new PhutilURI('/auth/start/')) return id(new PhutilURI('/auth/start/'))
->setQueryParam('next', (string)$this->getObjectURI()); ->setQueryParam('next', (string)$this->getObjectURI());