@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.

Apply hierarchical policy checks to Phriction

Summary: Ref T4029. When checking the view policy of a document, require the viewer to also be able to see all of the ancestors.

Test Plan:
- Hard-coded `/x/y/` to "no one".
- Checked that `/x/y/` is not visible.
- Checked that `/x/y/z/` is not visible.
- Checked that `/x/`, `/x/q/`, etc., are still visible.
- Tested project pages and sub-pages for project visibility.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T4029

Differential Revision: https://secure.phabricator.com/D9199

+126 -20
+1 -4
src/applications/phriction/controller/PhrictionDocumentController.php
··· 1 1 <?php 2 2 3 - /** 4 - * @group phriction 5 - */ 6 3 final class PhrictionDocumentController 7 4 extends PhrictionController { 8 5 ··· 118 115 $new_doc = id(new PhrictionDocumentQuery()) 119 116 ->setViewer($user) 120 117 ->withIDs(array($new_doc_id)) 121 - ->exectueOne(); 118 + ->executeOne(); 122 119 123 120 $slug_uri = PhrictionDocument::getSlugURI($new_doc->getSlug()); 124 121
+2 -2
src/applications/phriction/phid/PhrictionPHIDTypeDocument.php
··· 25 25 array $phids) { 26 26 27 27 return id(new PhrictionDocumentQuery()) 28 - ->needContent(true) 29 - ->withPHIDs($phids); 28 + ->withPHIDs($phids) 29 + ->needContent(true); 30 30 } 31 31 32 32 public function loadHandles(
+80 -4
src/applications/phriction/query/PhrictionDocumentQuery.php
··· 49 49 } 50 50 51 51 protected function loadPage() { 52 - $document = new PhrictionDocument(); 53 - $conn_r = $document->establishConnection('r'); 52 + $table = new PhrictionDocument(); 53 + $conn_r = $table->establishConnection('r'); 54 54 55 55 $rows = queryfx_all( 56 56 $conn_r, 57 57 'SELECT * FROM %T %Q %Q %Q', 58 - $document->getTableName(), 58 + $table->getTableName(), 59 59 $this->buildWhereClause($conn_r), 60 60 $this->buildOrderClause($conn_r), 61 61 $this->buildLimitClause($conn_r)); 62 62 63 - return $document->loadAllFromArray($rows); 63 + $documents = $table->loadAllFromArray($rows); 64 + 65 + if ($documents) { 66 + $ancestor_slugs = array(); 67 + foreach ($documents as $key => $document) { 68 + $document_slug = $document->getSlug(); 69 + foreach (PhabricatorSlug::getAncestry($document_slug) as $ancestor) { 70 + $ancestor_slugs[$ancestor][] = $key; 71 + } 72 + } 73 + 74 + if ($ancestor_slugs) { 75 + $ancestors = queryfx_all( 76 + $conn_r, 77 + 'SELECT * FROM %T WHERE slug IN (%Ls)', 78 + $document->getTableName(), 79 + array_keys($ancestor_slugs)); 80 + $ancestors = $table->loadAllFromArray($ancestors); 81 + $ancestors = mpull($ancestors, null, 'getSlug'); 82 + 83 + foreach ($ancestor_slugs as $ancestor_slug => $document_keys) { 84 + $ancestor = idx($ancestors, $ancestor_slug); 85 + foreach ($document_keys as $document_key) { 86 + $documents[$document_key]->attachAncestor( 87 + $ancestor_slug, 88 + $ancestor); 89 + } 90 + } 91 + } 92 + } 93 + 94 + return $documents; 64 95 } 65 96 66 97 protected function willFilterPage(array $documents) { 98 + // To view a Phriction document, you must also be able to view all of the 99 + // ancestor documents. Filter out documents which have ancestors that are 100 + // not visible. 101 + 102 + $document_map = array(); 103 + foreach ($documents as $document) { 104 + $document_map[$document->getSlug()] = $document; 105 + foreach ($document->getAncestors() as $key => $ancestor) { 106 + if ($ancestor) { 107 + $document_map[$key] = $ancestor; 108 + } 109 + } 110 + } 111 + 112 + $filtered_map = $this->applyPolicyFilter( 113 + $document_map, 114 + array(PhabricatorPolicyCapability::CAN_VIEW)); 115 + 116 + // Filter all of the documents where a parent is not visible. 117 + foreach ($documents as $document_key => $document) { 118 + // If the document itself is not visible, filter it. 119 + if (!isset($filtered_map[$document->getSlug()])) { 120 + $this->didRejectResult($documents[$document_key]); 121 + unset($documents[$document_key]); 122 + continue; 123 + } 124 + 125 + // If an ancestor exists but is not visible, filter the document. 126 + foreach ($document->getAncestors() as $ancestor_key => $ancestor) { 127 + if (!$ancestor) { 128 + continue; 129 + } 130 + 131 + if (!isset($filtered_map[$ancestor_key])) { 132 + $this->didRejectResult($documents[$document_key]); 133 + unset($documents[$document_key]); 134 + break; 135 + } 136 + } 137 + } 138 + 139 + if (!$documents) { 140 + return $documents; 141 + } 142 + 67 143 if ($this->needContent) { 68 144 $contents = id(new PhrictionContent())->loadAllWhere( 69 145 'id IN (%Ld)',
+23 -3
src/applications/phriction/storage/PhrictionDocument.php
··· 1 1 <?php 2 2 3 - /** 4 - * @group phriction 5 - */ 6 3 final class PhrictionDocument extends PhrictionDAO 7 4 implements 8 5 PhabricatorPolicyInterface, ··· 16 13 protected $status; 17 14 18 15 private $contentObject = self::ATTACHABLE; 16 + private $ancestors = array(); 19 17 20 18 // TODO: This should be `self::ATTACHABLE`, but there are still a lot of call 21 19 // sites which load PhrictionDocuments directly. ··· 84 82 return (bool)$this->getProject(); 85 83 } 86 84 85 + public function getAncestors() { 86 + return $this->ancestors; 87 + } 88 + 89 + public function getAncestor($slug) { 90 + return $this->assertAttachedKey($this->ancestors, $slug); 91 + } 92 + 93 + public function attachAncestor($slug, $ancestor) { 94 + $this->ancestors[$slug] = $ancestor; 95 + return $this; 96 + } 97 + 87 98 public static function isProjectSlug($slug) { 88 99 $slug = PhabricatorSlug::normalize($slug); 89 100 $prefix = 'projects/'; ··· 119 130 if ($this->hasProject()) { 120 131 return $this->getProject()->getPolicy($capability); 121 132 } 133 + 122 134 return PhabricatorPolicies::POLICY_USER; 123 135 } 124 136 ··· 134 146 return pht( 135 147 "This is a project wiki page, and inherits the project's policies."); 136 148 } 149 + 150 + switch ($capability) { 151 + case PhabricatorPolicyCapability::CAN_VIEW: 152 + return pht( 153 + 'To view a wiki document, you must also be able to view all '. 154 + 'of its parents.'); 155 + } 156 + 137 157 return null; 138 158 } 139 159
+20 -7
src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php
··· 302 302 private function getPolicyFilter() { 303 303 $filter = new PhabricatorPolicyFilter(); 304 304 $filter->setViewer($this->viewer); 305 - if (!$this->capabilities) { 306 - $capabilities = array( 307 - PhabricatorPolicyCapability::CAN_VIEW, 308 - ); 309 - } else { 310 - $capabilities = $this->capabilities; 311 - } 305 + $capabilities = $this->getRequiredCapabilities(); 312 306 $filter->requireCapabilities($capabilities); 313 307 $filter->raisePolicyExceptions($this->shouldRaisePolicyExceptions()); 314 308 315 309 return $filter; 310 + } 311 + 312 + protected function getRequiredCapabilities() { 313 + if ($this->capabilities) { 314 + return $this->capabilities; 315 + } 316 + 317 + return array( 318 + PhabricatorPolicyCapability::CAN_VIEW, 319 + ); 320 + } 321 + 322 + protected function applyPolicyFilter(array $objects, array $capabilities) { 323 + if ($this->shouldDisablePolicyFiltering()) { 324 + return $objects; 325 + } 326 + $filter = $this->getPolicyFilter(); 327 + $filter->requireCapabilities($capabilities); 328 + return $filter->apply($objects); 316 329 } 317 330 318 331 protected function didRejectResult(PhabricatorPolicyInterface $object) {