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

Allow PolicyRules to serve as "Object Policies"

Summary:
Ref T5681. Ref T8488. This allows policy rules to provide "Object Policies", which are similar to the global/basic policies:

- They show up directly in the dropdown (you don't have to create a custom rule).
- They don't need to create or load anything in the database.

To implement one, you just add a couple methods on an existing PolicyRule that let Phabricator know it can work as an object policy rule.

{F494764}

These rules only show up where they make sense. For example, the "Task Author" rule is only available in Maniphest, and in "Default View Policy" / "Default Edit Policy" of the Application config.

This should make T8488 easier by letting us set the default policies to "Members of Thread", without having to create a dedicated custom policy for every thread.

Test Plan:
- Set tasks to "Task Author" policy.
- Tried to view them as other users.
- Viewed transaction change strings.
- Viewed policy errors.
- Set them as default policies.
- Verified they don't leak into other policy controls.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T5681, T8488

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

+300 -8
+12
src/applications/maniphest/policyrule/ManiphestTaskAuthorPolicyRule.php
··· 3 3 final class ManiphestTaskAuthorPolicyRule 4 4 extends PhabricatorPolicyRule { 5 5 6 + public function getObjectPolicyKey() { 7 + return 'maniphest.author'; 8 + } 9 + 10 + public function getObjectPolicyName() { 11 + return pht('Task Author'); 12 + } 13 + 14 + public function getPolicyExplanation() { 15 + return pht('The author of this task can take this action.'); 16 + } 17 + 6 18 public function getRuleDescription() { 7 19 return pht('task author'); 8 20 }
+14
src/applications/meta/controller/PhabricatorApplicationEditController.php
··· 136 136 137 137 $template = $application->getCapabilityTemplatePHIDType($capability); 138 138 if ($template) { 139 + $phid_types = PhabricatorPHIDType::getAllTypes(); 140 + $phid_type = idx($phid_types, $template); 141 + if ($phid_type) { 142 + $template_object = $phid_type->newObject(); 143 + if ($template_object) { 144 + $template_policies = id(new PhabricatorPolicyQuery()) 145 + ->setViewer($user) 146 + ->setObject($template_object) 147 + ->execute(); 148 + $control->setPolicies($template_policies); 149 + $control->setTemplateObject($template_object); 150 + } 151 + } 152 + 139 153 $control->setTemplatePHIDType($template); 140 154 } 141 155
+7 -3
src/applications/policy/constants/PhabricatorPolicyType.php
··· 3 3 final class PhabricatorPolicyType extends PhabricatorPolicyConstants { 4 4 5 5 const TYPE_GLOBAL = 'global'; 6 + const TYPE_OBJECT = 'object'; 6 7 const TYPE_USER = 'user'; 7 8 const TYPE_CUSTOM = 'custom'; 8 9 const TYPE_PROJECT = 'project'; ··· 11 12 public static function getPolicyTypeOrder($type) { 12 13 static $map = array( 13 14 self::TYPE_GLOBAL => 0, 14 - self::TYPE_USER => 1, 15 - self::TYPE_CUSTOM => 2, 16 - self::TYPE_PROJECT => 3, 15 + self::TYPE_OBJECT => 1, 16 + self::TYPE_USER => 2, 17 + self::TYPE_CUSTOM => 3, 18 + self::TYPE_PROJECT => 4, 17 19 self::TYPE_MASKED => 9, 18 20 ); 19 21 return idx($map, $type, 9); ··· 23 25 switch ($type) { 24 26 case self::TYPE_GLOBAL: 25 27 return pht('Basic Policies'); 28 + case self::TYPE_OBJECT: 29 + return pht('Object Policies'); 26 30 case self::TYPE_USER: 27 31 return pht('User Policies'); 28 32 case self::TYPE_CUSTOM:
+67
src/applications/policy/filter/PhabricatorPolicyFilter.php
··· 8 8 private $raisePolicyExceptions; 9 9 private $userProjects; 10 10 private $customPolicies = array(); 11 + private $objectPolicies = array(); 11 12 private $forcedPolicy; 12 13 13 14 public static function mustRetainCapability( ··· 131 132 132 133 $need_projects = array(); 133 134 $need_policies = array(); 135 + $need_objpolicies = array(); 134 136 foreach ($objects as $key => $object) { 135 137 $object_capabilities = $object->getCapabilities(); 136 138 foreach ($capabilities as $capability) { ··· 143 145 } 144 146 145 147 $policy = $this->getObjectPolicy($object, $capability); 148 + 149 + if (PhabricatorPolicyQuery::isObjectPolicy($policy)) { 150 + $need_objpolicies[$policy][] = $object; 151 + continue; 152 + } 153 + 146 154 $type = phid_get_type($policy); 147 155 if ($type == PhabricatorProjectProjectPHIDType::TYPECONST) { 148 156 $need_projects[$policy] = $policy; 157 + continue; 149 158 } 150 159 151 160 if ($type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) { 152 161 $need_policies[$policy][] = $object; 162 + continue; 153 163 } 154 164 } 165 + } 166 + 167 + if ($need_objpolicies) { 168 + $this->loadObjectPolicies($need_objpolicies); 155 169 } 156 170 157 171 if ($need_policies) { ··· 486 500 $this->rejectObject($object, $policy, $capability); 487 501 break; 488 502 default: 503 + if (PhabricatorPolicyQuery::isObjectPolicy($policy)) { 504 + if ($this->checkObjectPolicy($policy, $object)) { 505 + return true; 506 + } else { 507 + $this->rejectObject($object, $policy, $capability); 508 + break; 509 + } 510 + } 511 + 489 512 $type = phid_get_type($policy); 490 513 if ($type == PhabricatorProjectProjectPHIDType::TYPECONST) { 491 514 if (!empty($this->userProjects[$viewer->getPHID()][$policy])) { ··· 573 596 throw $exception; 574 597 } 575 598 599 + private function loadObjectPolicies(array $map) { 600 + $viewer = $this->viewer; 601 + $viewer_phid = $viewer->getPHID(); 602 + 603 + $rules = PhabricatorPolicyQuery::getObjectPolicyRules(null); 604 + 605 + $results = array(); 606 + foreach ($map as $key => $object_list) { 607 + $rule = idx($rules, $key); 608 + if (!$rule) { 609 + continue; 610 + } 611 + 612 + foreach ($object_list as $object_key => $object) { 613 + if (!$rule->canApplyToObject($object)) { 614 + unset($object_list[$object_key]); 615 + } 616 + } 617 + 618 + $rule->willApplyRules($viewer, array(), $object_list); 619 + $results[$key] = $rule; 620 + } 621 + 622 + $this->objectPolicies[$viewer_phid] = $results; 623 + } 624 + 576 625 private function loadCustomPolicies(array $map) { 577 626 $viewer = $this->viewer; 578 627 $viewer_phid = $viewer->getPHID(); ··· 625 674 } 626 675 627 676 $this->customPolicies[$viewer->getPHID()] += $custom_policies; 677 + } 678 + 679 + private function checkObjectPolicy( 680 + $policy_phid, 681 + PhabricatorPolicyInterface $object) { 682 + $viewer = $this->viewer; 683 + $viewer_phid = $viewer->getPHID(); 684 + 685 + $rule = idx($this->objectPolicies[$viewer_phid], $policy_phid); 686 + if (!$rule) { 687 + return false; 688 + } 689 + 690 + if (!$rule->canApplyToObject($object)) { 691 + return false; 692 + } 693 + 694 + return $rule->applyRule($viewer, null, $object); 628 695 } 629 696 630 697 private function checkCustomPolicy(
+111 -2
src/applications/policy/query/PhabricatorPolicyQuery.php
··· 6 6 private $object; 7 7 private $phids; 8 8 9 + const OBJECT_POLICY_PREFIX = 'obj.'; 10 + 9 11 public function setObject(PhabricatorPolicyInterface $object) { 10 12 $this->object = $object; 11 13 return $this; ··· 71 73 $results = array(); 72 74 73 75 // First, load global policies. 74 - foreach ($this->getGlobalPolicies() as $phid => $policy) { 76 + foreach (self::getGlobalPolicies() as $phid => $policy) { 77 + if (isset($phids[$phid])) { 78 + $results[$phid] = $policy; 79 + unset($phids[$phid]); 80 + } 81 + } 82 + 83 + // Now, load object policies. 84 + foreach (self::getObjectPolicies($this->object) as $phid => $policy) { 75 85 if (isset($phids[$phid])) { 76 86 $results[$phid] = $policy; 77 87 unset($phids[$phid]); ··· 212 222 // option unless the object already has a "Public" policy. In this case we 213 223 // retain the policy but enforce it as though it was "All Users". 214 224 $show_public = PhabricatorEnv::getEnvConfig('policy.allow-public'); 215 - foreach ($this->getGlobalPolicies() as $phid => $policy) { 225 + foreach (self::getGlobalPolicies() as $phid => $policy) { 216 226 if ($phid == PhabricatorPolicies::POLICY_PUBLIC) { 217 227 if (!$show_public) { 218 228 continue; 219 229 } 220 230 } 231 + $phids[] = $phid; 232 + } 233 + 234 + foreach (self::getObjectPolicies($this->object) as $phid => $policy) { 221 235 $phids[] = $phid; 222 236 } 223 237 ··· 233 247 public function getQueryApplicationClass() { 234 248 return 'PhabricatorPolicyApplication'; 235 249 } 250 + 251 + public static function isSpecialPolicy($identifier) { 252 + if (self::isObjectPolicy($identifier)) { 253 + return true; 254 + } 255 + 256 + if (self::isGlobalPolicy($identifier)) { 257 + return true; 258 + } 259 + 260 + return false; 261 + } 262 + 263 + 264 + /* -( Object Policies )---------------------------------------------------- */ 265 + 266 + 267 + public static function isObjectPolicy($identifier) { 268 + $prefix = self::OBJECT_POLICY_PREFIX; 269 + return !strncmp($identifier, $prefix, strlen($prefix)); 270 + } 271 + 272 + public static function getObjectPolicy($identifier) { 273 + if (!self::isObjectPolicy($identifier)) { 274 + return null; 275 + } 276 + 277 + $policies = self::getObjectPolicies(null); 278 + return idx($policies, $identifier); 279 + } 280 + 281 + public static function getObjectPolicyRule($identifier) { 282 + if (!self::isObjectPolicy($identifier)) { 283 + return null; 284 + } 285 + 286 + $rules = self::getObjectPolicyRules(null); 287 + return idx($rules, $identifier); 288 + } 289 + 290 + public static function getObjectPolicies($object) { 291 + $rule_map = self::getObjectPolicyRules($object); 292 + 293 + $results = array(); 294 + foreach ($rule_map as $key => $rule) { 295 + $results[$key] = id(new PhabricatorPolicy()) 296 + ->setType(PhabricatorPolicyType::TYPE_OBJECT) 297 + ->setPHID($key) 298 + ->setIcon($rule->getObjectPolicyIcon()) 299 + ->setName($rule->getObjectPolicyName()) 300 + ->setShortName($rule->getObjectPolicyShortName()) 301 + ->makeEphemeral(); 302 + } 303 + 304 + return $results; 305 + } 306 + 307 + public static function getObjectPolicyRules($object) { 308 + $rules = id(new PhutilSymbolLoader()) 309 + ->setAncestorClass('PhabricatorPolicyRule') 310 + ->loadObjects(); 311 + 312 + $results = array(); 313 + foreach ($rules as $rule) { 314 + $key = $rule->getObjectPolicyKey(); 315 + if (!$key) { 316 + continue; 317 + } 318 + 319 + $full_key = self::OBJECT_POLICY_PREFIX.$key; 320 + if (isset($results[$full_key])) { 321 + throw new Exception( 322 + pht( 323 + 'Two policy rules (of classes "%s" and "%s") define the same '. 324 + 'object policy key ("%s"), but each object policy rule must use '. 325 + 'a unique key.', 326 + get_class($rule), 327 + get_class($results[$full_key]), 328 + $key)); 329 + } 330 + 331 + $results[$full_key] = $rule; 332 + } 333 + 334 + if ($object !== null) { 335 + foreach ($results as $key => $rule) { 336 + if (!$rule->canApplyToObject($object)) { 337 + unset($results[$key]); 338 + } 339 + } 340 + } 341 + 342 + return $results; 343 + } 344 + 236 345 237 346 }
+37
src/applications/policy/rule/PhabricatorPolicyRule.php
··· 1 1 <?php 2 2 3 + /** 4 + * @task objectpolicy Implementing Object Policies 5 + */ 3 6 abstract class PhabricatorPolicyRule { 4 7 5 8 const CONTROL_TYPE_TEXT = 'text'; ··· 91 94 */ 92 95 public function ruleHasEffect($value) { 93 96 return true; 97 + } 98 + 99 + 100 + /* -( Implementing Object Policies )--------------------------------------- */ 101 + 102 + 103 + /** 104 + * Return a unique string like "maniphest.author" to expose this rule as an 105 + * object policy. 106 + * 107 + * Object policy rules, like "Task Author", are more advanced than basic 108 + * policy rules (like "All Users") but not as powerful as custom rules. 109 + * 110 + * @return string Unique identifier for this rule. 111 + * @task objectpolicy 112 + */ 113 + public function getObjectPolicyKey() { 114 + return null; 115 + } 116 + 117 + public function getObjectPolicyName() { 118 + throw new PhutilMethodNotImplementedException(); 119 + } 120 + 121 + public function getObjectPolicyShortName() { 122 + return $this->getObjectPolicyName(); 123 + } 124 + 125 + public function getObjectPolicyIcon() { 126 + return 'fa-cube'; 127 + } 128 + 129 + public function getPolicyExplanation() { 130 + throw new PhutilMethodNotImplementedException(); 94 131 } 95 132 96 133 }
+19
src/applications/policy/storage/PhabricatorPolicy.php
··· 54 54 return PhabricatorPolicyQuery::getGlobalPolicy($policy_identifier); 55 55 } 56 56 57 + $policy = PhabricatorPolicyQuery::getObjectPolicy($policy_identifier); 58 + if ($policy) { 59 + return $policy; 60 + } 61 + 57 62 if (!$handle) { 58 63 throw new Exception( 59 64 pht( ··· 158 163 return $this->workflow; 159 164 } 160 165 166 + public function setIcon($icon) { 167 + $this->icon = $icon; 168 + return $this; 169 + } 170 + 161 171 public function getIcon() { 172 + if ($this->icon) { 173 + return $this->icon; 174 + } 175 + 162 176 switch ($this->getType()) { 163 177 case PhabricatorPolicyType::TYPE_GLOBAL: 164 178 static $map = array( ··· 203 217 public static function getPolicyExplanation( 204 218 PhabricatorUser $viewer, 205 219 $policy) { 220 + 221 + $rule = PhabricatorPolicyQuery::getObjectPolicyRule($policy); 222 + if ($rule) { 223 + return $rule->getPolicyExplanation(); 224 + } 206 225 207 226 switch ($policy) { 208 227 case PhabricatorPolicies::POLICY_PUBLIC:
+2 -2
src/applications/transactions/storage/PhabricatorApplicationTransaction.php
··· 243 243 case PhabricatorTransactions::TYPE_EDIT_POLICY: 244 244 case PhabricatorTransactions::TYPE_VIEW_POLICY: 245 245 case PhabricatorTransactions::TYPE_JOIN_POLICY: 246 - if (!PhabricatorPolicyQuery::isGlobalPolicy($old)) { 246 + if (!PhabricatorPolicyQuery::isSpecialPolicy($old)) { 247 247 $phids[] = array($old); 248 248 } 249 - if (!PhabricatorPolicyQuery::isGlobalPolicy($new)) { 249 + if (!PhabricatorPolicyQuery::isSpecialPolicy($new)) { 250 250 $phids[] = array($new); 251 251 } 252 252 break;
+31 -1
src/view/form/control/AphrontFormPolicyControl.php
··· 7 7 private $policies; 8 8 private $spacePHID; 9 9 private $templatePHIDType; 10 + private $templateObject; 10 11 11 12 public function setPolicyObject(PhabricatorPolicyInterface $object) { 12 13 $this->object = $object; ··· 30 31 31 32 public function setTemplatePHIDType($type) { 32 33 $this->templatePHIDType = $type; 34 + return $this; 35 + } 36 + 37 + public function setTemplateObject($object) { 38 + $this->templateObject = $object; 33 39 return $this; 34 40 } 35 41 ··· 64 70 65 71 protected function getOptions() { 66 72 $capability = $this->capability; 73 + $policies = $this->policies; 74 + 75 + // Exclude object policies which don't make sense here. This primarily 76 + // filters object policies associated from template capabilities (like 77 + // "Default Task View Policy" being set to "Task Author") so they aren't 78 + // made available on non-template capabilities (like "Can Bulk Edit"). 79 + foreach ($policies as $key => $policy) { 80 + if ($policy->getType() != PhabricatorPolicyType::TYPE_OBJECT) { 81 + continue; 82 + } 83 + 84 + $rule = PhabricatorPolicyQuery::getObjectPolicyRule($policy->getPHID()); 85 + if (!$rule) { 86 + continue; 87 + } 88 + 89 + $target = nonempty($this->templateObject, $this->object); 90 + if (!$rule->canApplyToObject($target)) { 91 + unset($policies[$key]); 92 + continue; 93 + } 94 + } 67 95 68 96 $options = array(); 69 - foreach ($this->policies as $policy) { 97 + foreach ($policies as $policy) { 70 98 if ($policy->getPHID() == PhabricatorPolicies::POLICY_PUBLIC) { 71 99 // Never expose "Public" for capabilities which don't support it. 72 100 $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability); ··· 74 102 continue; 75 103 } 76 104 } 105 + 77 106 $policy_short_name = id(new PhutilUTF8StringTruncator()) 78 107 ->setMaximumGlyphs(28) 79 108 ->truncateString($policy->getName()); ··· 122 151 $options, 123 152 array( 124 153 PhabricatorPolicyType::TYPE_GLOBAL, 154 + PhabricatorPolicyType::TYPE_OBJECT, 125 155 PhabricatorPolicyType::TYPE_USER, 126 156 PhabricatorPolicyType::TYPE_CUSTOM, 127 157 PhabricatorPolicyType::TYPE_PROJECT,