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

Address a transaction issue with some audit actions not applying correctly

Summary:
See <https://discourse.phabricator-community.org/t/cannot-accept-commits-in-audit/2166/>.

In D19842, I changed `PhabricatorEditField->shouldGenerateTransactionsFromComment()`.

- Previously, it bailed on `getIsConduitOnly()`.
- After the patch, it bails on a missing `getCommentActionLabel()`.

The old code was actually wrong, and it was previously possible to apply possibly-invalid actions in some cases (or, at least, sneak them through this layer: they would only actually apply if not validated properly).

In practice, it let a different bug through: we sometimes loaded commits without loading their audit authority, so testing whether the viewer could "Accept" the commit or not (or take some other actions like "Raise Concern") would always fail and throw an exception: "Trying to access data not attached to this object..."

Fixing the insufficiently-strict transaction generation code exposed the "authority not attached" bug, which caused some actions to fail to generate transactions.

This appeared in the UI as either an unhelpful error ("You can't post an empty comment") or an action with no effect. The unhelpful error was because we show that error if you aren't taking any //other// actions, and we wouldn't generate an "Accept" action because of the interaction of these bugs, so the code thought you were just posting an empty comment.

Test Plan: Without leaving comments, accepted and rejected commits. No more error messages, and actions took effect.

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: stephan.senkbeil, hskiba

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

+108 -75
-4
src/applications/audit/editor/PhabricatorAuditEditor.php
··· 59 59 60 60 $this->oldAuditStatus = $object->getAuditStatus(); 61 61 62 - $object->loadAndAttachAuditAuthority( 63 - $this->getActor(), 64 - $this->getActingAsPHID()); 65 - 66 62 return parent::expandTransactions($object, $xactions); 67 63 } 68 64
+1 -1
src/applications/diffusion/controller/DiffusionCommitController.php
··· 45 45 ->withIdentifiers(array($commit_identifier)) 46 46 ->needCommitData(true) 47 47 ->needAuditRequests(true) 48 + ->needAuditAuthority(array($viewer)) 48 49 ->setLimit(100) 49 50 ->needIdentities(true) 50 51 ->execute(); ··· 111 112 } 112 113 113 114 $audit_requests = $commit->getAudits(); 114 - $commit->loadAndAttachAuditAuthority($viewer); 115 115 116 116 $commit_data = $commit->getCommitData(); 117 117 $is_foreign = $commit_data->getCommitDetail('foreign-svn-stub');
+4 -1
src/applications/diffusion/editor/DiffusionCommitEditEngine.php
··· 43 43 } 44 44 45 45 protected function newObjectQuery() { 46 + $viewer = $this->getViewer(); 47 + 46 48 return id(new DiffusionCommitQuery()) 47 49 ->needCommitData(true) 48 - ->needAuditRequests(true); 50 + ->needAuditRequests(true) 51 + ->needAuditAuthority(array($viewer)); 49 52 } 50 53 51 54 protected function getEditorURI() {
+89 -3
src/applications/diffusion/query/DiffusionCommitQuery.php
··· 17 17 private $unreachable; 18 18 19 19 private $needAuditRequests; 20 + private $needAuditAuthority; 20 21 private $auditIDs; 21 22 private $auditorPHIDs; 22 23 private $epochMin; ··· 118 119 119 120 public function needAuditRequests($need) { 120 121 $this->needAuditRequests = $need; 122 + return $this; 123 + } 124 + 125 + public function needAuditAuthority(array $users) { 126 + assert_instances_of($users, 'PhabricatorUser'); 127 + $this->needAuditAuthority = $users; 121 128 return $this; 122 129 } 123 130 ··· 231 238 } 232 239 233 240 if (count($subqueries) > 1) { 234 - foreach ($subqueries as $key => $subquery) { 235 - $subqueries[$key] = '('.$subquery.')'; 241 + $unions = null; 242 + foreach ($subqueries as $subquery) { 243 + if (!$unions) { 244 + $unions = qsprintf( 245 + $conn, 246 + '(%Q)', 247 + $subquery); 248 + continue; 249 + } 250 + 251 + $unions = qsprintf( 252 + $conn, 253 + '%Q UNION DISTINCT (%Q)', 254 + $unions, 255 + $subquery); 236 256 } 237 257 238 258 $query = qsprintf( 239 259 $conn, 240 260 '%Q %Q %Q', 241 - implode(' UNION DISTINCT ', $subqueries), 261 + $unions, 242 262 $this->buildOrderClause($conn, true), 243 263 $this->buildLimitClause($conn)); 244 264 } else { ··· 421 441 PhabricatorDraftEngine::attachDrafts( 422 442 $viewer, 423 443 $commits); 444 + } 445 + 446 + if ($this->needAuditAuthority) { 447 + $authority_users = $this->needAuditAuthority; 448 + 449 + // NOTE: This isn't very efficient since we're running two queries per 450 + // user, but there's currently no way to figure out authority for 451 + // multiple users in one query. Today, we only ever request authority for 452 + // a single user and single commit, so this has no practical impact. 453 + 454 + // NOTE: We're querying with the viewership of query viewer, not the 455 + // actual users. If the viewer can't see a project or package, they 456 + // won't be able to see who has authority on it. This is safer than 457 + // showing them true authority, and should never matter today, but it 458 + // also doesn't seem like a significant disclosure and might be 459 + // reasonable to adjust later if it causes something weird or confusing 460 + // to happen. 461 + 462 + $authority_map = array(); 463 + foreach ($authority_users as $authority_user) { 464 + $authority_phid = $authority_user->getPHID(); 465 + if (!$authority_phid) { 466 + continue; 467 + } 468 + 469 + $result_phids = array(); 470 + 471 + // Users have authority over themselves. 472 + $result_phids[] = $authority_phid; 473 + 474 + // Users have authority over packages they own. 475 + $owned_packages = id(new PhabricatorOwnersPackageQuery()) 476 + ->setViewer($viewer) 477 + ->withAuthorityPHIDs(array($authority_phid)) 478 + ->execute(); 479 + foreach ($owned_packages as $package) { 480 + $result_phids[] = $package->getPHID(); 481 + } 482 + 483 + // Users have authority over projects they're members of. 484 + $projects = id(new PhabricatorProjectQuery()) 485 + ->setViewer($viewer) 486 + ->withMemberPHIDs(array($authority_phid)) 487 + ->execute(); 488 + foreach ($projects as $project) { 489 + $result_phids[] = $project->getPHID(); 490 + } 491 + 492 + $result_phids = array_fuse($result_phids); 493 + 494 + foreach ($commits as $commit) { 495 + $attach_phids = $result_phids; 496 + 497 + // NOTE: When modifying your own commits, you act only on behalf of 498 + // yourself, not your packages or projects. The idea here is that you 499 + // can't accept your own commits. In the future, this might change or 500 + // depend on configuration. 501 + $author_phid = $commit->getAuthorPHID(); 502 + if ($author_phid == $authority_phid) { 503 + $attach_phids = array($author_phid); 504 + $attach_phids = array_fuse($attach_phids); 505 + } 506 + 507 + $commit->attachAuditAuthority($authority_user, $attach_phids); 508 + } 509 + } 424 510 } 425 511 426 512 return $commits;
+14 -66
src/applications/repository/storage/PhabricatorRepositoryCommit.php
··· 210 210 return $this->assertAttached($this->committerIdentity); 211 211 } 212 212 213 - public function loadAndAttachAuditAuthority( 214 - PhabricatorUser $viewer, 215 - $actor_phid = null) { 216 - 217 - if ($actor_phid === null) { 218 - $actor_phid = $viewer->getPHID(); 219 - } 220 - 221 - // TODO: This method is a little weird and sketchy, but worlds better than 222 - // what came before it. Eventually, this should probably live in a Query 223 - // class. 224 - 225 - // Figure out which requests the actor has authority over: these are user 226 - // requests where they are the auditor, and packages and projects they are 227 - // a member of. 228 - 229 - if (!$actor_phid) { 230 - $attach_key = $viewer->getCacheFragment(); 231 - $phids = array(); 232 - } else { 233 - $attach_key = $actor_phid; 234 - // At least currently, when modifying your own commits, you act only on 235 - // behalf of yourself, not your packages/projects -- the idea being that 236 - // you can't accept your own commits. This may change or depend on 237 - // config. 238 - $actor_is_author = ($actor_phid == $this->getAuthorPHID()); 239 - if ($actor_is_author) { 240 - $phids = array($actor_phid); 241 - } else { 242 - $phids = array(); 243 - $phids[$actor_phid] = true; 244 - 245 - $owned_packages = id(new PhabricatorOwnersPackageQuery()) 246 - ->setViewer($viewer) 247 - ->withAuthorityPHIDs(array($actor_phid)) 248 - ->execute(); 249 - foreach ($owned_packages as $package) { 250 - $phids[$package->getPHID()] = true; 251 - } 252 - 253 - $projects = id(new PhabricatorProjectQuery()) 254 - ->setViewer($viewer) 255 - ->withMemberPHIDs(array($actor_phid)) 256 - ->execute(); 257 - foreach ($projects as $project) { 258 - $phids[$project->getPHID()] = true; 259 - } 213 + public function attachAuditAuthority( 214 + PhabricatorUser $user, 215 + array $authority) { 260 216 261 - $phids = array_keys($phids); 262 - } 217 + $user_phid = $user->getPHID(); 218 + if (!$user->getPHID()) { 219 + throw new Exception( 220 + pht('You can not attach audit authority for a user with no PHID.')); 263 221 } 264 222 265 - $this->auditAuthorityPHIDs[$attach_key] = array_fuse($phids); 223 + $this->auditAuthorityPHIDs[$user_phid] = $authority; 266 224 267 225 return $this; 268 226 } 269 227 270 228 public function hasAuditAuthority( 271 - PhabricatorUser $viewer, 272 - PhabricatorRepositoryAuditRequest $audit, 273 - $actor_phid = null) { 274 - 275 - if ($actor_phid === null) { 276 - $actor_phid = $viewer->getPHID(); 277 - } 278 - 279 - if (!$actor_phid) { 280 - $attach_key = $viewer->getCacheFragment(); 281 - } else { 282 - $attach_key = $actor_phid; 283 - } 284 - 285 - $map = $this->assertAttachedKey($this->auditAuthorityPHIDs, $attach_key); 229 + PhabricatorUser $user, 230 + PhabricatorRepositoryAuditRequest $audit) { 286 231 287 - if (!$actor_phid) { 232 + $user_phid = $user->getPHID(); 233 + if (!$user_phid) { 288 234 return false; 289 235 } 236 + 237 + $map = $this->assertAttachedKey($this->auditAuthorityPHIDs, $user_phid); 290 238 291 239 return isset($map[$audit->getAuditorPHID()]); 292 240 }