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

Make Herald transcripts policy-aware

Summary:
Ref T603. Herald transcripts potentially leak a bunch of content (task text, revision/commit content). Don't let users see them if they can't see the actual objects.

This is a little messy but ends up mostly reasonable-ish.

Test Plan:
- Verified that transcripts for objects I couldn't see no longer appear in the list, and reject access.
- Verified that transcripts for objects in applications I can't see reject access, albeit less gracefully.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T603

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

+162 -52
+2
src/__phutil_library_map__.php
··· 661 661 'HeraldTranscript' => 'applications/herald/storage/transcript/HeraldTranscript.php', 662 662 'HeraldTranscriptController' => 'applications/herald/controller/HeraldTranscriptController.php', 663 663 'HeraldTranscriptListController' => 'applications/herald/controller/HeraldTranscriptListController.php', 664 + 'HeraldTranscriptQuery' => 'applications/herald/query/HeraldTranscriptQuery.php', 664 665 'Javelin' => 'infrastructure/javelin/Javelin.php', 665 666 'JavelinReactorExample' => 'applications/uiexample/examples/JavelinReactorExample.php', 666 667 'JavelinUIExample' => 'applications/uiexample/examples/JavelinUIExample.php', ··· 2753 2754 'HeraldTranscript' => 'HeraldDAO', 2754 2755 'HeraldTranscriptController' => 'HeraldController', 2755 2756 'HeraldTranscriptListController' => 'HeraldController', 2757 + 'HeraldTranscriptQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2756 2758 'JavelinReactorExample' => 'PhabricatorUIExample', 2757 2759 'JavelinUIExample' => 'PhabricatorUIExample', 2758 2760 'JavelinViewExample' => 'PhabricatorUIExample',
-4
src/applications/herald/adapter/HeraldAdapter.php
··· 93 93 94 94 abstract public function applyHeraldEffects(array $effects); 95 95 96 - public function isEnabled() { 97 - return true; 98 - } 99 - 100 96 public function isAvailableToUser(PhabricatorUser $viewer) { 101 97 $applications = id(new PhabricatorApplicationQuery()) 102 98 ->setViewer($viewer)
+19 -4
src/applications/herald/controller/HeraldTranscriptController.php
··· 25 25 } 26 26 27 27 public function processRequest() { 28 + $request = $this->getRequest(); 29 + $viewer = $request->getUser(); 28 30 29 - $xscript = id(new HeraldTranscript())->load($this->id); 31 + $xscript = id(new HeraldTranscriptQuery()) 32 + ->setViewer($viewer) 33 + ->withIDs(array($this->id)) 34 + ->executeOne(); 30 35 if (!$xscript) { 31 - throw new Exception('Uknown transcript!'); 36 + return new Aphront404Response(); 32 37 } 33 38 34 39 require_celerity_resource('herald-test-css'); ··· 46 51 pht('Details of this transcript have been garbage collected.'))); 47 52 $nav->appendChild($notice); 48 53 } else { 54 + $map = HeraldAdapter::getEnabledAdapterMap($viewer); 55 + $object_type = $object_xscript->getType(); 56 + if (empty($map[$object_type])) { 57 + // TODO: We should filter these out in the Query, but we have to load 58 + // the objectTranscript right now, which is potentially enormous. We 59 + // should denormalize the object type, or move the data into a separate 60 + // table, and then filter this earlier (and thus raise a better error). 61 + // For now, just block access so we don't violate policies. 62 + throw new Exception( 63 + pht("This transcript has an invalid or inaccessible adapter.")); 64 + } 49 65 50 - $this->adapter = HeraldAdapter::getAdapterForContentType( 51 - $object_xscript->getType()); 66 + $this->adapter = HeraldAdapter::getAdapterForContentType($object_type); 52 67 53 68 $filter = $this->getFilterPHIDs(); 54 69 $this->filterTranscript($xscript, $filter);
+15 -43
src/applications/herald/controller/HeraldTranscriptListController.php
··· 7 7 $request = $this->getRequest(); 8 8 $user = $request->getUser(); 9 9 10 - // Get one page of data together with the pager. 11 - // Pull these objects manually since the serialized fields are gigantic. 12 - $transcript = new HeraldTranscript(); 13 - 14 - $conn_r = $transcript->establishConnection('r'); 15 - $phid = $request->getStr('phid'); 16 - $where_clause = ''; 17 - if ($phid) { 18 - $where_clause = qsprintf( 19 - $conn_r, 20 - 'WHERE objectPHID = %s', 21 - $phid); 22 - } 23 - 24 - $pager = new AphrontPagerView(); 25 - $pager->setOffset($request->getInt('offset')); 26 - $pager->setURI($request->getRequestURI(), 'offset'); 27 - 28 - $limit_clause = qsprintf( 29 - $conn_r, 30 - 'LIMIT %d, %d', 31 - $pager->getOffset(), 32 - $pager->getPageSize() + 1); 33 - 34 - $data = queryfx_all( 35 - $conn_r, 36 - 'SELECT id, objectPHID, time, duration, dryRun FROM %T 37 - %Q 38 - ORDER BY id DESC 39 - %Q', 40 - $transcript->getTableName(), 41 - $where_clause, 42 - $limit_clause); 10 + $pager = new AphrontCursorPagerView(); 11 + $pager->readFromRequest($request); 43 12 44 - $data = $pager->sliceResults($data); 13 + $transcripts = id(new HeraldTranscriptQuery()) 14 + ->setViewer($user) 15 + ->needPartialRecords(true) 16 + ->executeWithCursorPager($pager); 45 17 46 18 // Render the table. 47 19 $handles = array(); 48 - if ($data) { 49 - $phids = ipull($data, 'objectPHID', 'objectPHID'); 20 + if ($transcripts) { 21 + $phids = mpull($transcripts, 'getObjectPHID', 'getObjectPHID'); 50 22 $handles = $this->loadViewerHandles($phids); 51 23 } 52 24 53 25 $rows = array(); 54 - foreach ($data as $xscript) { 26 + foreach ($transcripts as $xscript) { 55 27 $rows[] = array( 56 - phabricator_date($xscript['time'], $user), 57 - phabricator_time($xscript['time'], $user), 58 - $handles[$xscript['objectPHID']]->renderLink(), 59 - $xscript['dryRun'] ? 'Yes' : '', 60 - number_format((int)(1000 * $xscript['duration'])).' ms', 28 + phabricator_date($xscript->getTime(), $user), 29 + phabricator_time($xscript->getTime(), $user), 30 + $handles[$xscript->getObjectPHID()]->renderLink(), 31 + $xscript->getDryRun() ? pht('Yes') : '', 32 + number_format((int)(1000 * $xscript->getDuration())).' ms', 61 33 phutil_tag( 62 34 'a', 63 35 array( 64 - 'href' => '/herald/transcript/'.$xscript['id'].'/', 36 + 'href' => '/herald/transcript/'.$xscript->getID().'/', 65 37 'class' => 'button small grey', 66 38 ), 67 39 pht('View Transcript')),
+98
src/applications/herald/query/HeraldTranscriptQuery.php
··· 1 + <?php 2 + 3 + final class HeraldTranscriptQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $ids; 7 + private $needPartialRecords; 8 + 9 + public function withIDs(array $ids) { 10 + $this->ids = $ids; 11 + return $this; 12 + } 13 + 14 + public function needPartialRecords($need_partial) { 15 + $this->needPartialRecords = $need_partial; 16 + return $this; 17 + } 18 + 19 + public function loadPage() { 20 + $transcript = new HeraldTranscript(); 21 + $conn_r = $transcript->establishConnection('r'); 22 + 23 + // NOTE: Transcripts include a potentially enormous amount of serialized 24 + // data, so we're loading only some of the fields here if the caller asked 25 + // for partial records. 26 + 27 + if ($this->needPartialRecords) { 28 + $fields = implode( 29 + ', ', 30 + array( 31 + 'id', 32 + 'phid', 33 + 'objectPHID', 34 + 'time', 35 + 'duration', 36 + 'dryRun', 37 + 'host', 38 + )); 39 + } else { 40 + $fields = '*'; 41 + } 42 + 43 + $rows = queryfx_all( 44 + $conn_r, 45 + 'SELECT %Q FROM %T t %Q %Q %Q', 46 + $fields, 47 + $transcript->getTableName(), 48 + $this->buildWhereClause($conn_r), 49 + $this->buildOrderClause($conn_r), 50 + $this->buildLimitClause($conn_r)); 51 + 52 + $transcripts = $transcript->loadAllFromArray($rows); 53 + 54 + if ($this->needPartialRecords) { 55 + // Make sure nothing tries to write these; they aren't complete. 56 + foreach ($transcripts as $transcript) { 57 + $transcript->makeEphemeral(); 58 + } 59 + } 60 + 61 + return $transcripts; 62 + } 63 + 64 + public function willFilterPage(array $transcripts) { 65 + $phids = mpull($transcripts, 'getObjectPHID'); 66 + 67 + $objects = id(new PhabricatorObjectQuery()) 68 + ->setViewer($this->getViewer()) 69 + ->withPHIDs($phids) 70 + ->execute(); 71 + 72 + foreach ($transcripts as $key => $transcript) { 73 + if (empty($objects[$transcript->getObjectPHID()])) { 74 + $this->didRejectResult($transcript); 75 + unset($transcripts[$key]); 76 + } 77 + } 78 + 79 + return $transcripts; 80 + } 81 + 82 + public function buildWhereClause(AphrontDatabaseConnection $conn_r) { 83 + $where = array(); 84 + 85 + if ($this->ids) { 86 + $where[] = qsprintf( 87 + $conn_r, 88 + 'id IN (%Ld)', 89 + $this->ids); 90 + } 91 + 92 + $where[] = $this->buildPagingClause($conn_r); 93 + 94 + return $this->formatWhereClause($where); 95 + } 96 + 97 + 98 + }
+28 -1
src/applications/herald/storage/transcript/HeraldTranscript.php
··· 1 1 <?php 2 2 3 - final class HeraldTranscript extends HeraldDAO { 3 + final class HeraldTranscript extends HeraldDAO 4 + implements PhabricatorPolicyInterface { 4 5 5 6 protected $id; 6 7 protected $phid; ··· 165 166 public function generatePHID() { 166 167 return PhabricatorPHID::generateNewPHID('HLXS'); 167 168 } 169 + 170 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 171 + 172 + public function getCapabilities() { 173 + return array( 174 + PhabricatorPolicyCapability::CAN_VIEW, 175 + ); 176 + } 177 + 178 + public function getPolicy($capability) { 179 + switch ($capability) { 180 + case PhabricatorPolicyCapability::CAN_VIEW: 181 + return PhabricatorPolicies::POLICY_USER; 182 + } 183 + } 184 + 185 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 186 + return false; 187 + } 188 + 189 + public function describeAutomaticCapability($capability) { 190 + return pht( 191 + 'To view a transcript, you must be able to view the object the '. 192 + 'transcript is about.'); 193 + } 194 + 168 195 169 196 }