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

Tighten up some policy interactions in Herald

Summary:
Ref T603. Herald is a bit of a policy minefield right now, although I think pretty much everything has straightforward solutions. This change:

- Introduces "create" and "create global" permisions for Herald.
- Maybe "create" is sort of redundant since there's no reason to have access to the application if not creating rules, but I think this won't be the case for most applications, so having an explicit "create" permission is more consistent.
- Add some application policy helper functions.
- Improve rendering a bit -- I think we probably need to build some `PolicyType` class, similar to `PHIDType`, to really get this right.
- Don't let users who can't use application X create Herald rules for application X.
- Remove Maniphest/Pholio rules when those applications are not installed.

Test Plan:
- Restricted access to Maniphest and uninstalled Pholio.
- Verified Pholio rules no longer appear for anyone.
- Verified Maniphest ruls no longer appear for restricted users.
- Verified users without CREATE_GLOBAL can not create global ruls.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T603

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

+139 -35
+19
src/applications/base/controller/PhabricatorController.php
··· 350 350 return $view; 351 351 } 352 352 353 + protected function hasApplicationCapability($capability) { 354 + return PhabricatorPolicyFilter::hasCapability( 355 + $this->getRequest()->getUser(), 356 + $this->getCurrentApplication(), 357 + $capability); 358 + } 359 + 360 + protected function requireApplicationCapability($capability) { 361 + PhabricatorPolicyFilter::requireCapability( 362 + $this->getRequest()->getUser(), 363 + $this->getCurrentApplication(), 364 + $capability); 365 + } 366 + 367 + protected function explainApplicationCapability($capability, $message) { 368 + // TODO: Render a link to get more information. 369 + return $message; 370 + } 371 + 353 372 }
+17 -13
src/applications/herald/adapter/HeraldAdapter.php
··· 97 97 return true; 98 98 } 99 99 100 + public function isAvailableToUser(PhabricatorUser $viewer) { 101 + $applications = id(new PhabricatorApplicationQuery()) 102 + ->setViewer($viewer) 103 + ->withInstalled(true) 104 + ->withClasses(array($this->getAdapterApplicationClass())) 105 + ->execute(); 106 + 107 + return !empty($applications); 108 + } 109 + 110 + 100 111 /** 101 112 * NOTE: You generally should not override this; it exists to support legacy 102 113 * adapters which had hard-coded content types. ··· 106 117 } 107 118 108 119 abstract public function getAdapterContentName(); 120 + abstract public function getAdapterApplicationClass(); 109 121 110 122 111 123 /* -( Fields )------------------------------------------------------------- */ ··· 694 706 return $adapters; 695 707 } 696 708 697 - public static function getAllEnabledAdapters() { 698 - $adapters = self::getAllAdapters(); 699 - foreach ($adapters as $key => $adapter) { 700 - if (!$adapter->isEnabled()) { 701 - unset($adapters[$key]); 702 - } 703 - } 704 - return $adapters; 705 - } 706 - 707 709 public static function getAdapterForContentType($content_type) { 708 710 $adapters = self::getAllAdapters(); 709 711 ··· 719 721 $content_type)); 720 722 } 721 723 722 - public static function getEnabledAdapterMap() { 724 + public static function getEnabledAdapterMap(PhabricatorUser $viewer) { 723 725 $map = array(); 724 726 725 - $adapters = HeraldAdapter::getAllEnabledAdapters(); 727 + $adapters = HeraldAdapter::getAllAdapters(); 726 728 foreach ($adapters as $adapter) { 729 + if (!$adapter->isAvailableToUser($viewer)) { 730 + continue; 731 + } 727 732 $type = $adapter->getAdapterContentType(); 728 733 $name = $adapter->getAdapterContentName(); 729 734 $map[$type] = $name; ··· 732 737 asort($map); 733 738 return $map; 734 739 } 735 - 736 740 737 741 public function renderRuleAsText(HeraldRule $rule, array $handles) { 738 742 assert_instances_of($handles, 'PhabricatorObjectHandle');
+2 -3
src/applications/herald/adapter/HeraldCommitAdapter.php
··· 27 27 protected $affectedPackages; 28 28 protected $auditNeededPackages; 29 29 30 - public function isEnabled() { 31 - $app = 'PhabricatorApplicationDiffusion'; 32 - return PhabricatorApplication::isClassInstalled($app); 30 + public function getAdapterApplicationClass() { 31 + return 'PhabricatorApplicationDiffusion'; 33 32 } 34 33 35 34 public function getAdapterContentType() {
+2 -3
src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php
··· 20 20 protected $affectedPackages; 21 21 protected $changesets; 22 22 23 - public function isEnabled() { 24 - $app = 'PhabricatorApplicationDifferential'; 25 - return PhabricatorApplication::isClassInstalled($app); 23 + public function getAdapterApplicationClass() { 24 + return 'PhabricatorApplicationDifferential'; 26 25 } 27 26 28 27 public function getAdapterContentType() {
+4
src/applications/herald/adapter/HeraldManiphestTaskAdapter.php
··· 10 10 private $assignPHID; 11 11 private $projectPHIDs = array(); 12 12 13 + public function getAdapterApplicationClass() { 14 + return 'PhabricatorApplicationManiphest'; 15 + } 16 + 13 17 public function setTask(ManiphestTask $task) { 14 18 $this->task = $task; 15 19 return $this;
+4
src/applications/herald/adapter/HeraldPholioMockAdapter.php
··· 8 8 private $mock; 9 9 private $ccPHIDs = array(); 10 10 11 + public function getAdapterApplicationClass() { 12 + return 'PhabricatorApplicationPholio'; 13 + } 14 + 11 15 public function setMock(PholioMock $mock) { 12 16 $this->mock = $mock; 13 17 return $this;
+17
src/applications/herald/application/PhabricatorApplicationHerald.php
··· 2 2 3 3 final class PhabricatorApplicationHerald extends PhabricatorApplication { 4 4 5 + const CAN_CREATE_RULE = 'herald.create'; 6 + const CAN_CREATE_GLOBAL_RULE = 'herald.global'; 7 + 5 8 public function getBaseURI() { 6 9 return '/herald/'; 7 10 } ··· 47 50 ), 48 51 ); 49 52 } 53 + 54 + protected function getCustomCapabilities() { 55 + return array( 56 + self::CAN_CREATE_RULE => array( 57 + 'label' => pht('Can Create Rules'), 58 + ), 59 + self::CAN_CREATE_GLOBAL_RULE => array( 60 + 'label' => pht('Can Create Global Rules'), 61 + 'caption' => pht('Global rules can bypass access controls.'), 62 + 'default' => PhabricatorPolicies::POLICY_ADMIN, 63 + ), 64 + ); 65 + } 66 + 50 67 51 68 }
+5 -1
src/applications/herald/controller/HeraldController.php
··· 23 23 public function buildApplicationCrumbs() { 24 24 $crumbs = parent::buildApplicationCrumbs(); 25 25 26 + $can_create = $this->hasApplicationCapability( 27 + PhabricatorApplicationHerald::CAN_CREATE_RULE); 28 + 26 29 $crumbs->addAction( 27 30 id(new PHUIListItemView()) 28 31 ->setName(pht('Create Herald Rule')) 29 32 ->setHref($this->getApplicationURI('new/')) 30 - ->setIcon('create')); 33 + ->setIcon('create') 34 + ->setDisabled(!$can_create)); 31 35 32 36 return $crumbs; 33 37 }
+33 -8
src/applications/herald/controller/HeraldNewController.php
··· 11 11 } 12 12 13 13 public function processRequest() { 14 - 15 14 $request = $this->getRequest(); 16 15 $user = $request->getUser(); 17 16 18 - $content_type_map = HeraldAdapter::getEnabledAdapterMap(); 17 + $this->requireApplicationCapability( 18 + PhabricatorApplicationHerald::CAN_CREATE_RULE); 19 + 20 + $can_global = $this->hasApplicationCapability( 21 + PhabricatorApplicationHerald::CAN_CREATE_GLOBAL_RULE); 22 + 23 + $content_type_map = HeraldAdapter::getEnabledAdapterMap($user); 19 24 if (empty($content_type_map[$this->contentType])) { 20 25 $this->contentType = head_key($content_type_map); 21 26 } ··· 32 37 HeraldRuleTypeConfig::RULE_TYPE_PERSONAL, 33 38 )) + $rule_type_map; 34 39 40 + if (!$can_global) { 41 + $global_link = $this->explainApplicationCapability( 42 + PhabricatorApplicationHerald::CAN_CREATE_GLOBAL_RULE, 43 + pht('You do not have permission to create or manage global rules.')); 44 + } else { 45 + $global_link = null; 46 + } 47 + 35 48 $captions = array( 36 49 HeraldRuleTypeConfig::RULE_TYPE_PERSONAL => 37 - pht('Personal rules notify you about events. You own them, but '. 38 - 'they can only affect you.'), 50 + pht( 51 + 'Personal rules notify you about events. You own them, but they can '. 52 + 'only affect you.'), 39 53 HeraldRuleTypeConfig::RULE_TYPE_GLOBAL => 40 - pht('Global rules notify anyone about events. No one owns them, and '. 41 - 'anyone can edit them. Usually, Global rules are used to notify '. 42 - 'mailing lists.'), 54 + phutil_implode_html( 55 + phutil_tag('br'), 56 + array_filter( 57 + array( 58 + pht( 59 + 'Global rules notify anyone about events. Global rules can '. 60 + 'bypass access control policies.'), 61 + $global_link, 62 + ))), 43 63 ); 44 64 45 65 $radio = id(new AphrontFormRadioButtonControl()) ··· 48 68 ->setValue($this->ruleType); 49 69 50 70 foreach ($rule_type_map as $value => $name) { 71 + $disabled = ($value == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) && 72 + (!$can_global); 73 + 51 74 $radio->addButton( 52 75 $value, 53 76 $name, 54 - idx($captions, $value)); 77 + idx($captions, $value), 78 + $disabled ? 'disabled' : null, 79 + $disabled); 55 80 } 56 81 57 82 $form = id(new AphrontFormView())
+10 -2
src/applications/herald/controller/HeraldRuleController.php
··· 14 14 $request = $this->getRequest(); 15 15 $user = $request->getUser(); 16 16 17 - $content_type_map = HeraldAdapter::getEnabledAdapterMap(); 17 + $content_type_map = HeraldAdapter::getEnabledAdapterMap($user); 18 18 $rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap(); 19 19 20 20 if ($this->id) { ··· 42 42 43 43 $rule_type = $request->getStr('rule_type'); 44 44 if (!isset($rule_type_map[$rule_type])) { 45 - $rule_type = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL; 45 + $rule_type = HeraldRuleTypeConfig::RULE_TYPE_PERSONAL; 46 46 } 47 47 $rule->setRuleType($rule_type); 48 48 49 49 $cancel_uri = $this->getApplicationURI(); 50 + 51 + $this->requireApplicationCapability( 52 + PhabricatorApplicationHerald::CAN_CREATE_RULE); 53 + } 54 + 55 + if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) { 56 + $this->requireApplicationCapability( 57 + PhabricatorApplicationHerald::CAN_CREATE_GLOBAL_RULE); 50 58 } 51 59 52 60 $adapter = HeraldAdapter::getAdapterForContentType($rule->getContentType());
+1 -1
src/applications/herald/controller/HeraldRuleListController.php
··· 33 33 $phids = mpull($rules, 'getAuthorPHID'); 34 34 $this->loadHandles($phids); 35 35 36 - $content_type_map = HeraldAdapter::getEnabledAdapterMap(); 36 + $content_type_map = HeraldAdapter::getEnabledAdapterMap($viewer); 37 37 38 38 $list = id(new PHUIObjectItemListView()) 39 39 ->setUser($viewer);
+3 -1
src/applications/herald/controller/HeraldRuleViewController.php
··· 96 96 if ($adapter) { 97 97 $view->addProperty( 98 98 pht('Applies To'), 99 - idx(HeraldAdapter::getEnabledAdapterMap(), $rule->getContentType())); 99 + idx( 100 + HeraldAdapter::getEnabledAdapterMap($viewer), 101 + $rule->getContentType())); 100 102 101 103 $view->invokeWillRenderEvent(); 102 104
+10
src/applications/herald/query/HeraldRuleQuery.php
··· 76 76 public function willFilterPage(array $rules) { 77 77 $rule_ids = mpull($rules, 'getID'); 78 78 79 + // Filter out any rules that have invalid adapters, or have adapters the 80 + // viewer isn't permitted to see or use (for example, Differential rules 81 + // if the user can't use Differential or Differential is not installed). 82 + $types = HeraldAdapter::getEnabledAdapterMap($this->getViewer()); 83 + foreach ($rules as $key => $rule) { 84 + if (empty($types[$rule->getContentType()])) { 85 + unset($rules[$key]); 86 + } 87 + } 88 + 79 89 if ($this->needValidateAuthors) { 80 90 $this->validateRuleAuthors($rules); 81 91 }
+4 -2
src/applications/herald/query/HeraldRuleSearchEngine.php
··· 110 110 private function getContentTypeOptions() { 111 111 return array( 112 112 '' => pht('(All Content Types)'), 113 - ) + HeraldAdapter::getEnabledAdapterMap(); 113 + ) + HeraldAdapter::getEnabledAdapterMap($this->requireViewer()); 114 114 } 115 115 116 116 private function getContentTypeValues() { 117 - return array_fuse(array_keys(HeraldAdapter::getEnabledAdapterMap())); 117 + return array_fuse( 118 + array_keys( 119 + HeraldAdapter::getEnabledAdapterMap($this->requireViewer()))); 118 120 } 119 121 120 122 private function getRuleTypeOptions() {
+8 -1
src/applications/policy/filter/PhabricatorPolicyFilter.php
··· 281 281 case PhabricatorPolicyCapability::CAN_JOIN: 282 282 $message = pht('You do not have permission to join this object.'); 283 283 break; 284 + default: 285 + // TODO: Farm these out to applications? 286 + $message = pht( 287 + 'You do not have a required capability ("%s") to do whatever you '. 288 + 'are trying to do.', 289 + $capability); 290 + break; 284 291 } 285 292 286 293 switch ($policy) { ··· 369 376 } 370 377 371 378 $more = array_merge( 372 - array($more), 379 + array_filter(array($more)), 373 380 array_filter((array)$object->describeAutomaticCapability($capability))); 374 381 375 382 $exception = new PhabricatorPolicyException($message);