@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+350 -47
+15
resources/sql/patches/20131211.phragmentedges.sql
··· 1 + CREATE TABLE {$NAMESPACE}_phragment.edge ( 2 + src VARCHAR(64) NOT NULL COLLATE utf8_bin, 3 + type VARCHAR(64) NOT NULL COLLATE utf8_bin, 4 + dst VARCHAR(64) NOT NULL COLLATE utf8_bin, 5 + dateCreated INT UNSIGNED NOT NULL, 6 + seq INT UNSIGNED NOT NULL, 7 + dataID INT UNSIGNED, 8 + PRIMARY KEY (src, type, dst), 9 + KEY (src, type, dateCreated, seq) 10 + ) ENGINE=InnoDB, COLLATE utf8_general_ci; 11 + 12 + CREATE TABLE {$NAMESPACE}_phragment.edgedata ( 13 + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 14 + data LONGTEXT NOT NULL COLLATE utf8_bin 15 + ) ENGINE=InnoDB, COLLATE utf8_general_ci;
+4
src/__phutil_library_map__.php
··· 2180 2180 'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php', 2181 2181 'PhortuneWePayPaymentProvider' => 'applications/phortune/provider/PhortuneWePayPaymentProvider.php', 2182 2182 'PhragmentBrowseController' => 'applications/phragment/controller/PhragmentBrowseController.php', 2183 + 'PhragmentCapabilityCanCreate' => 'applications/phragment/capability/PhragmentCapabilityCanCreate.php', 2183 2184 'PhragmentController' => 'applications/phragment/controller/PhragmentController.php', 2184 2185 'PhragmentCreateController' => 'applications/phragment/controller/PhragmentCreateController.php', 2185 2186 'PhragmentDAO' => 'applications/phragment/storage/PhragmentDAO.php', ··· 2193 2194 'PhragmentPHIDTypeSnapshot' => 'applications/phragment/phid/PhragmentPHIDTypeSnapshot.php', 2194 2195 'PhragmentPatchController' => 'applications/phragment/controller/PhragmentPatchController.php', 2195 2196 'PhragmentPatchUtil' => 'applications/phragment/util/PhragmentPatchUtil.php', 2197 + 'PhragmentPolicyController' => 'applications/phragment/controller/PhragmentPolicyController.php', 2196 2198 'PhragmentRevertController' => 'applications/phragment/controller/PhragmentRevertController.php', 2197 2199 'PhragmentSnapshot' => 'applications/phragment/storage/PhragmentSnapshot.php', 2198 2200 'PhragmentSnapshotChild' => 'applications/phragment/storage/PhragmentSnapshotChild.php', ··· 4790 4792 'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider', 4791 4793 'PhortuneWePayPaymentProvider' => 'PhortunePaymentProvider', 4792 4794 'PhragmentBrowseController' => 'PhragmentController', 4795 + 'PhragmentCapabilityCanCreate' => 'PhabricatorPolicyCapability', 4793 4796 'PhragmentController' => 'PhabricatorController', 4794 4797 'PhragmentCreateController' => 'PhragmentController', 4795 4798 'PhragmentDAO' => 'PhabricatorLiskDAO', ··· 4811 4814 'PhragmentPHIDTypeSnapshot' => 'PhabricatorPHIDType', 4812 4815 'PhragmentPatchController' => 'PhragmentController', 4813 4816 'PhragmentPatchUtil' => 'Phobject', 4817 + 'PhragmentPolicyController' => 'PhragmentController', 4814 4818 'PhragmentRevertController' => 'PhragmentController', 4815 4819 'PhragmentSnapshot' => 4816 4820 array(
+1
src/applications/files/application/PhabricatorApplicationFiles.php
··· 61 61 'xform/(?P<transform>[^/]+)/(?P<phid>[^/]+)/(?P<key>[^/]+)/' 62 62 => 'PhabricatorFileTransformController', 63 63 'uploaddialog/' => 'PhabricatorFileUploadDialogController', 64 + 'download/(?P<phid>[^/]+)/' => 'PhabricatorFileDialogController', 64 65 ), 65 66 ); 66 67 }
+6 -6
src/applications/files/controller/PhabricatorFileDataController.php
··· 66 66 if ($is_viewable && !$force_download) { 67 67 $response->setMimeType($file->getViewableMimeType()); 68 68 } else { 69 - if (!$request->isHTTPPost()) { 70 - // NOTE: Require POST to download files. We'd rather go full-bore and 71 - // do a real CSRF check, but can't currently authenticate users on the 72 - // file domain. This should blunt any attacks based on iframes, script 73 - // tags, applet tags, etc., at least. Send the user to the "info" page 74 - // if they're using some other method. 69 + if (!$request->isHTTPPost() && !$alt_domain) { 70 + // NOTE: Require POST to download files from the primary domain. We'd 71 + // rather go full-bore and do a real CSRF check, but can't currently 72 + // authenticate users on the file domain. This should blunt any 73 + // attacks based on iframes, script tags, applet tags, etc., at least. 74 + // Send the user to the "info" page if they're using some other method. 75 75 return id(new AphrontRedirectResponse()) 76 76 ->setURI(PhabricatorEnv::getProductionURI($file->getBestURI())); 77 77 }
+8
src/applications/phragment/application/PhabricatorApplicationPhragment.php
··· 37 37 'browse/(?P<dblob>.*)' => 'PhragmentBrowseController', 38 38 'create/(?P<dblob>.*)' => 'PhragmentCreateController', 39 39 'update/(?P<dblob>.*)' => 'PhragmentUpdateController', 40 + 'policy/(?P<dblob>.*)' => 'PhragmentPolicyController', 40 41 'history/(?P<dblob>.*)' => 'PhragmentHistoryController', 41 42 'zip/(?P<dblob>.*)' => 'PhragmentZIPController', 42 43 'zip@(?P<snapshot>[^/]+)/(?P<dblob>.*)' => 'PhragmentZIPController', ··· 52 53 '(?P<id>[0-9]*)/' => 'PhragmentSnapshotPromoteController', 53 54 ), 54 55 ), 56 + ), 57 + ); 58 + } 59 + 60 + protected function getCustomCapabilities() { 61 + return array( 62 + PhragmentCapabilityCanCreate::CAPABILITY => array( 55 63 ), 56 64 ); 57 65 }
+20
src/applications/phragment/capability/PhragmentCapabilityCanCreate.php
··· 1 + <?php 2 + 3 + final class PhragmentCapabilityCanCreate 4 + extends PhabricatorPolicyCapability { 5 + 6 + const CAPABILITY = 'phragment.create'; 7 + 8 + public function getCapabilityKey() { 9 + return self::CAPABILITY; 10 + } 11 + 12 + public function getCapabilityName() { 13 + return pht('Can Create Fragments'); 14 + } 15 + 16 + public function describeCapabilityRejection() { 17 + return pht('You do not have permission to create fragments.'); 18 + } 19 + 20 + }
+13 -5
src/applications/phragment/controller/PhragmentBrowseController.php
··· 4 4 5 5 private $dblob; 6 6 7 + public function shouldAllowPublic() { 8 + return true; 9 + } 10 + 7 11 public function willProcessRequest(array $data) { 8 12 $this->dblob = idx($data, "dblob", ""); 9 13 } ··· 24 28 } 25 29 26 30 $crumbs = $this->buildApplicationCrumbsWithPath($parents); 27 - $crumbs->addAction( 28 - id(new PHUIListItemView()) 29 - ->setName(pht('Create Fragment')) 30 - ->setHref($this->getApplicationURI('/create/'.$path)) 31 - ->setIcon('create')); 31 + if ($this->hasApplicationCapability( 32 + PhragmentCapabilityCanCreate::CAPABILITY)) { 33 + $crumbs->addAction( 34 + id(new PHUIListItemView()) 35 + ->setName(pht('Create Fragment')) 36 + ->setHref($this->getApplicationURI('/create/'.$path)) 37 + ->setIcon('create')); 38 + } 32 39 33 40 $current_box = $this->createCurrentFragmentView($current, false); 34 41 ··· 79 86 return $this->buildApplicationPage( 80 87 array( 81 88 $crumbs, 89 + $this->renderConfigurationWarningIfRequired(), 82 90 $current_box, 83 91 $list), 84 92 array(
+55 -9
src/applications/phragment/controller/PhragmentController.php
··· 84 84 ->withPHIDs(array($fragment->getLatestVersion()->getFilePHID())) 85 85 ->executeOne(); 86 86 if ($file !== null) { 87 - $file_uri = $file->getBestURI(); 87 + $file_uri = $file->getDownloadURI(); 88 88 } 89 89 } 90 90 ··· 93 93 ->setPolicyObject($fragment) 94 94 ->setUser($viewer); 95 95 96 + $can_edit = PhabricatorPolicyFilter::hasCapability( 97 + $viewer, 98 + $fragment, 99 + PhabricatorPolicyCapability::CAN_EDIT); 100 + 101 + $zip_uri = $this->getApplicationURI("zip/".$fragment->getPath()); 102 + 96 103 $actions = id(new PhabricatorActionListView()) 97 104 ->setUser($viewer) 98 105 ->setObject($fragment) ··· 100 107 $actions->addAction( 101 108 id(new PhabricatorActionView()) 102 109 ->setName(pht('Download Fragment')) 103 - ->setHref($file_uri) 104 - ->setDisabled($file === null) 110 + ->setHref($this->isCorrectlyConfigured() ? $file_uri : null) 111 + ->setDisabled($file === null || !$this->isCorrectlyConfigured()) 105 112 ->setIcon('download')); 106 113 $actions->addAction( 107 114 id(new PhabricatorActionView()) 108 115 ->setName(pht('Download Contents as ZIP')) 109 - ->setHref($this->getApplicationURI("zip/".$fragment->getPath())) 110 - ->setDisabled(false) // TODO: Policy 116 + ->setHref($this->isCorrectlyConfigured() ? $zip_uri : null) 117 + ->setDisabled(!$this->isCorrectlyConfigured()) 111 118 ->setIcon('zip')); 112 119 if (!$fragment->isDirectory()) { 113 120 $actions->addAction( 114 121 id(new PhabricatorActionView()) 115 122 ->setName(pht('Update Fragment')) 116 123 ->setHref($this->getApplicationURI("update/".$fragment->getPath())) 117 - ->setDisabled(false) // TODO: Policy 124 + ->setDisabled(!$can_edit) 125 + ->setWorkflow(!$can_edit) 118 126 ->setIcon('edit')); 119 127 } else { 120 128 $actions->addAction( 121 129 id(new PhabricatorActionView()) 122 130 ->setName(pht('Convert to File')) 123 131 ->setHref($this->getApplicationURI("update/".$fragment->getPath())) 124 - ->setDisabled(false) // TODO: Policy 132 + ->setDisabled(!$can_edit) 133 + ->setWorkflow(!$can_edit) 125 134 ->setIcon('edit')); 126 135 } 136 + $actions->addAction( 137 + id(new PhabricatorActionView()) 138 + ->setName(pht('Set Fragment Policies')) 139 + ->setHref($this->getApplicationURI("policy/".$fragment->getPath())) 140 + ->setDisabled(!$can_edit) 141 + ->setWorkflow(!$can_edit) 142 + ->setIcon('edit')); 127 143 if ($is_history_view) { 128 144 $actions->addAction( 129 145 id(new PhabricatorActionView()) ··· 142 158 ->setName(pht('Create Snapshot')) 143 159 ->setHref($this->getApplicationURI( 144 160 "snapshot/create/".$fragment->getPath())) 145 - ->setDisabled(false) // TODO: Policy 161 + ->setDisabled(!$can_edit) 162 + ->setWorkflow(!$can_edit) 146 163 ->setIcon('snapshot')); 147 164 $actions->addAction( 148 165 id(new PhabricatorActionView()) ··· 150 167 ->setHref($this->getApplicationURI( 151 168 "snapshot/promote/latest/".$fragment->getPath())) 152 169 ->setWorkflow(true) 153 - ->setDisabled(false) // TODO: Policy 170 + ->setDisabled(!$can_edit) 154 171 ->setIcon('promote')); 155 172 156 173 $properties = id(new PHUIPropertyListView()) ··· 186 203 return id(new PHUIObjectBoxView()) 187 204 ->setHeader($header) 188 205 ->addPropertyList($properties); 206 + } 207 + 208 + function renderConfigurationWarningIfRequired() { 209 + $alt = PhabricatorEnv::getEnvConfig("security.alternate-file-domain"); 210 + if ($alt === null) { 211 + return id(new AphrontErrorView()) 212 + ->setTitle(pht('security.alternate-file-domain must be configured!')) 213 + ->setSeverity(AphrontErrorView::SEVERITY_ERROR) 214 + ->appendChild(phutil_tag('p', array(), pht( 215 + 'Because Phragment generates files (such as ZIP archives and '. 216 + 'patches) as they are requested, it requires that you configure '. 217 + 'the `security.alterate-file-domain` option. This option on it\'s '. 218 + 'own will also provide additional security when serving files '. 219 + 'across Phabricator.'))); 220 + } 221 + return null; 222 + } 223 + 224 + /** 225 + * We use this to disable the download links if the alternate domain is 226 + * not configured correctly. Although the download links will mostly work 227 + * for logged in users without an alternate domain, the behaviour is 228 + * reasonably non-consistent and will deny public users, even if policies 229 + * are configured otherwise (because the Files app does not support showing 230 + * the info page to viewers who are not logged in). 231 + */ 232 + function isCorrectlyConfigured() { 233 + $alt = PhabricatorEnv::getEnvConfig("security.alternate-file-domain"); 234 + return $alt !== null; 189 235 } 190 236 191 237 }
+1
src/applications/phragment/controller/PhragmentCreateController.php
··· 123 123 return $this->buildApplicationPage( 124 124 array( 125 125 $crumbs, 126 + $this->renderConfigurationWarningIfRequired(), 126 127 $box), 127 128 array( 128 129 'title' => pht('Create Fragment'),
+24 -9
src/applications/phragment/controller/PhragmentHistoryController.php
··· 4 4 5 5 private $dblob; 6 6 7 + public function shouldAllowPublic() { 8 + return true; 9 + } 10 + 7 11 public function willProcessRequest(array $data) { 8 12 $this->dblob = idx($data, "dblob", ""); 9 13 } ··· 21 25 $path = $current->getPath(); 22 26 23 27 $crumbs = $this->buildApplicationCrumbsWithPath($parents); 24 - $crumbs->addAction( 25 - id(new PHUIListItemView()) 26 - ->setName(pht('Create Fragment')) 27 - ->setHref($this->getApplicationURI('/create/'.$path)) 28 - ->setIcon('create')); 28 + if ($this->hasApplicationCapability( 29 + PhragmentCapabilityCanCreate::CAPABILITY)) { 30 + $crumbs->addAction( 31 + id(new PHUIListItemView()) 32 + ->setName(pht('Create Fragment')) 33 + ->setHref($this->getApplicationURI('/create/'.$path)) 34 + ->setIcon('create')); 35 + } 29 36 30 37 $current_box = $this->createCurrentFragmentView($current, true); 31 38 ··· 44 51 ->execute(); 45 52 $files = mpull($files, null, 'getPHID'); 46 53 54 + $can_edit = PhabricatorPolicyFilter::hasCapability( 55 + $viewer, 56 + $current, 57 + PhabricatorPolicyCapability::CAN_EDIT); 58 + 47 59 $first = true; 48 60 foreach ($versions as $version) { 49 61 $item = id(new PHUIObjectItemView()); ··· 58 70 $item->addAttribute('Deletion'); 59 71 } 60 72 61 - if (!$first) { 73 + if (!$first && $can_edit) { 62 74 $item->addAction(id(new PHUIListItemView()) 63 75 ->setIcon('undo') 64 76 ->setRenderNameAsTooltip(true) ··· 71 83 $disabled = !isset($files[$version->getFilePHID()]); 72 84 $action = id(new PHUIListItemView()) 73 85 ->setIcon('download') 74 - ->setDisabled($disabled) 86 + ->setDisabled($disabled || !$this->isCorrectlyConfigured()) 75 87 ->setRenderNameAsTooltip(true) 76 88 ->setName(pht("Download")); 77 - if (!$disabled) { 78 - $action->setHref($files[$version->getFilePHID()]->getBestURI()); 89 + if (!$disabled && $this->isCorrectlyConfigured()) { 90 + $action->setHref($files[$version->getFilePHID()] 91 + ->getDownloadURI($version->getURI())); 79 92 } 80 93 $item->addAction($action); 94 + 81 95 $list->addItem($item); 82 96 83 97 $first = false; ··· 86 100 return $this->buildApplicationPage( 87 101 array( 88 102 $crumbs, 103 + $this->renderConfigurationWarningIfRequired(), 89 104 $current_box, 90 105 $list), 91 106 array(
+18 -2
src/applications/phragment/controller/PhragmentPatchController.php
··· 5 5 private $aid; 6 6 private $bid; 7 7 8 + public function shouldAllowPublic() { 9 + return true; 10 + } 11 + 8 12 public function willProcessRequest(array $data) { 9 13 $this->aid = idx($data, "aid", 0); 10 14 $this->bid = idx($data, "bid", 0); ··· 61 65 $patch = PhragmentPatchUtil::calculatePatch($file_a, $file_b); 62 66 63 67 if ($patch === null) { 64 - throw new Exception("Unable to compute patch!"); 68 + // There are no differences between the two files, so we output 69 + // an empty patch. 70 + $patch = ''; 65 71 } 66 72 67 73 $a_sequence = 'x'; ··· 74 80 $a_sequence.'.'. 75 81 $version_b->getSequence().'.patch'; 76 82 83 + $return = $version_b->getURI(); 84 + if ($request->getExists('return')) { 85 + $return = $request->getStr('return'); 86 + } 87 + 77 88 $result = PhabricatorFile::buildFromFileDataOrHash( 78 89 $patch, 79 90 array( ··· 81 92 'mime-type' => 'text/plain', 82 93 'ttl' => time() + 60 * 60 * 24, 83 94 )); 95 + 96 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 97 + $result->attachToObject($viewer, $version_b->getFragmentPHID()); 98 + unset($unguarded); 99 + 84 100 return id(new AphrontRedirectResponse()) 85 - ->setURI($result->getBestURI()); 101 + ->setURI($result->getDownloadURI($return)); 86 102 } 87 103 88 104 }
+110
src/applications/phragment/controller/PhragmentPolicyController.php
··· 1 + <?php 2 + 3 + final class PhragmentPolicyController extends PhragmentController { 4 + 5 + private $dblob; 6 + 7 + public function willProcessRequest(array $data) { 8 + $this->dblob = idx($data, "dblob", ""); 9 + } 10 + 11 + public function processRequest() { 12 + $request = $this->getRequest(); 13 + $viewer = $request->getUser(); 14 + 15 + $parents = $this->loadParentFragments($this->dblob); 16 + if ($parents === null) { 17 + return new Aphront404Response(); 18 + } 19 + $fragment = idx($parents, count($parents) - 1, null); 20 + 21 + $error_view = null; 22 + 23 + if ($request->isFormPost()) { 24 + $errors = array(); 25 + 26 + $v_view_policy = $request->getStr('viewPolicy'); 27 + $v_edit_policy = $request->getStr('editPolicy'); 28 + $v_replace_children = $request->getBool('replacePoliciesOnChildren'); 29 + 30 + $fragment->setViewPolicy($v_view_policy); 31 + $fragment->setEditPolicy($v_edit_policy); 32 + 33 + $fragment->save(); 34 + 35 + if ($v_replace_children) { 36 + // If you can edit a fragment, you can forcibly set the policies 37 + // on child fragments, regardless of whether you can see them or not. 38 + $children = id(new PhragmentFragmentQuery()) 39 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 40 + ->withLeadingPath($fragment->getPath().'/') 41 + ->execute(); 42 + $children_phids = mpull($children, 'getPHID'); 43 + 44 + $fragment->openTransaction(); 45 + foreach ($children as $child) { 46 + $child->setViewPolicy($v_view_policy); 47 + $child->setEditPolicy($v_edit_policy); 48 + $child->save(); 49 + } 50 + $fragment->saveTransaction(); 51 + } 52 + 53 + return id(new AphrontRedirectResponse()) 54 + ->setURI('/phragment/browse/'.$fragment->getPath()); 55 + } 56 + 57 + $policies = id(new PhabricatorPolicyQuery()) 58 + ->setViewer($viewer) 59 + ->setObject($fragment) 60 + ->execute(); 61 + 62 + $form = id(new AphrontFormView()) 63 + ->setUser($viewer) 64 + ->appendChild( 65 + id(new AphrontFormPolicyControl()) 66 + ->setName('viewPolicy') 67 + ->setPolicyObject($fragment) 68 + ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) 69 + ->setPolicies($policies)) 70 + ->appendChild( 71 + id(new AphrontFormPolicyControl()) 72 + ->setName('editPolicy') 73 + ->setPolicyObject($fragment) 74 + ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) 75 + ->setPolicies($policies)) 76 + ->appendChild( 77 + id(new AphrontFormCheckboxControl()) 78 + ->addCheckbox( 79 + 'replacePoliciesOnChildren', 80 + 'true', 81 + pht( 82 + 'Replace policies on child fragments with '. 83 + 'the policies above.'))) 84 + ->appendChild( 85 + id(new AphrontFormSubmitControl()) 86 + ->setValue(pht('Save Fragment Policies')) 87 + ->addCancelButton( 88 + $this->getApplicationURI('browse/'.$fragment->getPath()))); 89 + 90 + $crumbs = $this->buildApplicationCrumbsWithPath($parents); 91 + $crumbs->addCrumb( 92 + id(new PhabricatorCrumbView()) 93 + ->setName(pht('Edit Fragment Policies'))); 94 + 95 + $box = id(new PHUIObjectBoxView()) 96 + ->setHeaderText(pht('Edit Fragment Policies: %s', $fragment->getPath())) 97 + ->setValidationException(null) 98 + ->setForm($form); 99 + 100 + return $this->buildApplicationPage( 101 + array( 102 + $crumbs, 103 + $this->renderConfigurationWarningIfRequired(), 104 + $box), 105 + array( 106 + 'title' => pht('Edit Fragment Policies'), 107 + 'device' => true)); 108 + } 109 + 110 + }
+6
src/applications/phragment/controller/PhragmentSnapshotCreateController.php
··· 21 21 return new Aphront404Response(); 22 22 } 23 23 24 + PhabricatorPolicyFilter::requireCapability( 25 + $viewer, 26 + $fragment, 27 + PhabricatorPolicyCapability::CAN_EDIT); 28 + 24 29 $children = id(new PhragmentFragmentQuery()) 25 30 ->setViewer($viewer) 26 31 ->needLatestVersion(true) ··· 161 166 return $this->buildApplicationPage( 162 167 array( 163 168 $crumbs, 169 + $this->renderConfigurationWarningIfRequired(), 164 170 $box), 165 171 array( 166 172 'title' => pht('Create Fragment'),
+3
src/applications/phragment/controller/PhragmentSnapshotDeleteController.php
··· 14 14 15 15 $snapshot = id(new PhragmentSnapshotQuery()) 16 16 ->setViewer($viewer) 17 + ->requireCapabilities(array( 18 + PhabricatorPolicyCapability::CAN_VIEW, 19 + PhabricatorPolicyCapability::CAN_EDIT)) 17 20 ->withIDs(array($this->id)) 18 21 ->executeOne(); 19 22 if ($snapshot === null) {
+13 -1
src/applications/phragment/controller/PhragmentSnapshotPromoteController.php
··· 23 23 if ($this->dblob !== null) { 24 24 $this->targetFragment = id(new PhragmentFragmentQuery()) 25 25 ->setViewer($viewer) 26 + ->requireCapabilities(array( 27 + PhabricatorPolicyCapability::CAN_VIEW, 28 + PhabricatorPolicyCapability::CAN_EDIT)) 26 29 ->withPaths(array($this->dblob)) 27 30 ->executeOne(); 28 31 if ($this->targetFragment === null) { ··· 40 43 if ($this->id !== null) { 41 44 $this->targetSnapshot = id(new PhragmentSnapshotQuery()) 42 45 ->setViewer($viewer) 46 + ->requireCapabilities(array( 47 + PhabricatorPolicyCapability::CAN_VIEW, 48 + PhabricatorPolicyCapability::CAN_EDIT)) 43 49 ->withIDs(array($this->id)) 44 50 ->executeOne(); 45 51 if ($this->targetSnapshot === null) { ··· 141 147 } 142 148 $snapshot->saveTransaction(); 143 149 144 - return id(new AphrontRedirectResponse()); 150 + if ($this->id === null) { 151 + return id(new AphrontRedirectResponse()) 152 + ->setURI($this->targetFragment->getURI()); 153 + } else { 154 + return id(new AphrontRedirectResponse()) 155 + ->setURI($this->targetSnapshot->getURI()); 156 + } 145 157 } 146 158 147 159 return $this->createDialog();
+14 -4
src/applications/phragment/controller/PhragmentSnapshotViewController.php
··· 4 4 5 5 private $id; 6 6 7 + public function shouldAllowPublic() { 8 + return true; 9 + } 10 + 7 11 public function willProcessRequest(array $data) { 8 12 $this->id = idx($data, "id", ""); 9 13 } ··· 72 76 return $this->buildApplicationPage( 73 77 array( 74 78 $crumbs, 79 + $this->renderConfigurationWarningIfRequired(), 75 80 $box, 76 81 $list), 77 82 array( ··· 100 105 "zip@".$snapshot->getName(). 101 106 "/".$snapshot->getPrimaryFragment()->getPath()); 102 107 108 + $can_edit = PhabricatorPolicyFilter::hasCapability( 109 + $viewer, 110 + $snapshot, 111 + PhabricatorPolicyCapability::CAN_EDIT); 112 + 103 113 $actions = id(new PhabricatorActionListView()) 104 114 ->setUser($viewer) 105 115 ->setObject($snapshot) ··· 107 117 $actions->addAction( 108 118 id(new PhabricatorActionView()) 109 119 ->setName(pht('Download Snapshot as ZIP')) 110 - ->setHref($zip_uri) 111 - ->setDisabled(false) // TODO: Policy 120 + ->setHref($this->isCorrectlyConfigured() ? $zip_uri : null) 121 + ->setDisabled(!$this->isCorrectlyConfigured()) 112 122 ->setIcon('zip')); 113 123 $actions->addAction( 114 124 id(new PhabricatorActionView()) 115 125 ->setName(pht('Delete Snapshot')) 116 126 ->setHref($this->getApplicationURI( 117 127 "snapshot/delete/".$snapshot->getID()."/")) 128 + ->setDisabled(!$can_edit) 118 129 ->setWorkflow(true) 119 - ->setDisabled(false) // TODO: Policy 120 130 ->setIcon('delete')); 121 131 $actions->addAction( 122 132 id(new PhabricatorActionView()) 123 133 ->setName(pht('Promote Another Snapshot to Here')) 124 134 ->setHref($this->getApplicationURI( 125 135 "snapshot/promote/".$snapshot->getID()."/")) 136 + ->setDisabled(!$can_edit) 126 137 ->setWorkflow(true) 127 - ->setDisabled(false) // TODO: Policy 128 138 ->setIcon('promote')); 129 139 130 140 $properties = id(new PHUIPropertyListView())
+1
src/applications/phragment/controller/PhragmentUpdateController.php
··· 74 74 return $this->buildApplicationPage( 75 75 array( 76 76 $crumbs, 77 + $this->renderConfigurationWarningIfRequired(), 77 78 $box), 78 79 array( 79 80 'title' => pht('Update Fragment'),
+12 -5
src/applications/phragment/controller/PhragmentVersionController.php
··· 4 4 5 5 private $id; 6 6 7 + public function shouldAllowPublic() { 8 + return true; 9 + } 10 + 7 11 public function willProcessRequest(array $data) { 8 12 $this->id = idx($data, "id", 0); 9 13 } ··· 41 45 ->withPHIDs(array($version->getFilePHID())) 42 46 ->executeOne(); 43 47 if ($file !== null) { 44 - $file_uri = $file->getBestURI(); 48 + $file_uri = $file->getDownloadURI(); 45 49 } 46 50 47 51 $header = id(new PHUIHeaderView()) ··· 59 63 $actions->addAction( 60 64 id(new PhabricatorActionView()) 61 65 ->setName(pht('Download Version')) 62 - ->setHref($file_uri) 63 - ->setDisabled($file === null) 66 + ->setDisabled($file === null || !$this->isCorrectlyConfigured()) 67 + ->setHref($this->isCorrectlyConfigured() ? $file_uri : null) 64 68 ->setIcon('download')); 65 69 66 70 $properties = id(new PHUIPropertyListView()) ··· 78 82 return $this->buildApplicationPage( 79 83 array( 80 84 $crumbs, 85 + $this->renderConfigurationWarningIfRequired(), 81 86 $box, 82 87 $this->renderPatchFromPreviousVersion($version, $file), 83 88 $this->renderPreviousVersionList($version)), ··· 155 160 $item->addAttribute(phabricator_datetime( 156 161 $previous_version->getDateCreated(), 157 162 $viewer)); 163 + $patch_uri = $this->getApplicationURI( 164 + 'patch/'.$previous_version->getID().'/'.$version->getID()); 158 165 $item->addAction(id(new PHUIListItemView()) 159 166 ->setIcon('patch') 160 167 ->setName(pht("Get Patch")) 161 - ->setHref($this->getApplicationURI( 162 - 'patch/'.$previous_version->getID().'/'.$version->getID()))); 168 + ->setHref($this->isCorrectlyConfigured() ? $patch_uri : null) 169 + ->setDisabled(!$this->isCorrectlyConfigured())); 163 170 $list->addItem($item); 164 171 } 165 172
+18 -2
src/applications/phragment/controller/PhragmentZIPController.php
··· 7 7 8 8 private $snapshotCache; 9 9 10 + public function shouldAllowPublic() { 11 + return true; 12 + } 13 + 10 14 public function willProcessRequest(array $data) { 11 15 $this->dblob = idx($data, "dblob", ""); 12 16 $this->snapshot = idx($data, "snapshot", null); ··· 87 91 } 88 92 89 93 foreach ($mappings as $path => $file) { 90 - $zip->addFromString($path, $file->loadFileData()); 94 + if ($file !== null) { 95 + $zip->addFromString($path, $file->loadFileData()); 96 + } 91 97 } 92 98 $zip->close(); 93 99 ··· 103 109 'name' => $zip_name, 104 110 'ttl' => time() + 60 * 60 * 24, 105 111 )); 112 + 113 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 114 + $file->attachToObject($viewer, $fragment->getPHID()); 115 + unset($unguarded); 116 + 117 + $return = $fragment->getURI(); 118 + if ($request->getExists('return')) { 119 + $return = $request->getStr('return'); 120 + } 121 + 106 122 return id(new AphrontRedirectResponse()) 107 - ->setURI($file->getBestURI()); 123 + ->setURI($file->getDownloadURI($return)); 108 124 } 109 125 110 126 /**
+2
src/applications/phragment/storage/PhragmentFragment.php
··· 118 118 $this->setLatestVersionPHID($version->getPHID()); 119 119 $this->save(); 120 120 $this->saveTransaction(); 121 + 122 + $file->attachToObject($viewer, $version->getPHID()); 121 123 } 122 124 123 125 /**
+1 -3
src/applications/phragment/storage/PhragmentSnapshot.php
··· 48 48 49 49 50 50 public function getCapabilities() { 51 - return array( 52 - PhabricatorPolicyCapability::CAN_VIEW 53 - ); 51 + return $this->getPrimaryFragment()->getCapabilities(); 54 52 } 55 53 56 54 public function getPolicy($capability) {
+4
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 1828 1828 'type' => 'sql', 1829 1829 'name' => $this->getPatchPath('20131208.phragmentsnapshot.sql'), 1830 1830 ), 1831 + '20131211.phragmentedges.sql' => array( 1832 + 'type' => 'sql', 1833 + 'name' => $this->getPatchPath('20131211.phragmentedges.sql'), 1834 + ), 1831 1835 ); 1832 1836 } 1833 1837 }
+1 -1
src/view/layout/PhabricatorActionView.php
··· 40 40 * viewing. 41 41 */ 42 42 public function getHref() { 43 - if ($this->workflow || $this->renderAsForm) { 43 + if (($this->workflow || $this->renderAsForm) && !$this->download) { 44 44 if (!$this->user || !$this->user->isLoggedIn()) { 45 45 return id(new PhutilURI('/auth/start/')) 46 46 ->setQueryParam('next', (string)$this->getObjectURI());