@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 applications to define new policy capabilities

Summary:
Ref T603. I want to let applications define new capabilities (like "can manage global rules" in Herald) and get full support for them, including reasonable error strings in the UI.

Currently, this is difficult for a couple of reasons. Partly this is just a code organization issue, which is easy to fix. The bigger thing is that we have a bunch of strings which depend on both the policy and capability, like: "You must be an administrator to view this object." "Administrator" is the policy, and "view" is the capability.

That means every new capability has to add a string for each policy, and every new policy (should we introduce any) needs to add a string for each capability. And we can't do any piecemeal "You must be a {$role} to {$action} this object" becuase it's impossible to translate.

Instead, make all the strings depend on //only// the policy, //only// the capability, or //only// the object type. This makes the dialogs read a little more strangely, but I think it's still pretty easy to understand, and it makes adding new stuff way way easier.

Also provide more context, and more useful exception messages.

Test Plan:
- See screenshots.
- Also triggered a policy exception and verified it was dramatically more useful than it used to be.

Reviewers: btrahan, chad

Reviewed By: btrahan

CC: chad, aran

Maniphest Tasks: T603

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

+330 -227
+46 -46
src/__celerity_resource_map__.php
··· 836 836 ), 837 837 'aphront-dialog-view-css' => 838 838 array( 839 - 'uri' => '/res/609ccc78/rsrc/css/aphront/dialog-view.css', 839 + 'uri' => '/res/830fa2de/rsrc/css/aphront/dialog-view.css', 840 840 'type' => 'css', 841 841 'requires' => 842 842 array( ··· 4184 4184 ), array( 4185 4185 'packages' => 4186 4186 array( 4187 - 'cd37aa53' => 4187 + 'c98eaabf' => 4188 4188 array( 4189 4189 'name' => 'core.pkg.css', 4190 4190 'symbols' => ··· 4233 4233 41 => 'phabricator-tag-view-css', 4234 4234 42 => 'phui-list-view-css', 4235 4235 ), 4236 - 'uri' => '/res/pkg/cd37aa53/core.pkg.css', 4236 + 'uri' => '/res/pkg/c98eaabf/core.pkg.css', 4237 4237 'type' => 'css', 4238 4238 ), 4239 4239 '64eeda79' => ··· 4425 4425 ), 4426 4426 'reverse' => 4427 4427 array( 4428 - 'aphront-dialog-view-css' => 'cd37aa53', 4429 - 'aphront-error-view-css' => 'cd37aa53', 4430 - 'aphront-list-filter-view-css' => 'cd37aa53', 4431 - 'aphront-pager-view-css' => 'cd37aa53', 4432 - 'aphront-panel-view-css' => 'cd37aa53', 4433 - 'aphront-table-view-css' => 'cd37aa53', 4434 - 'aphront-tokenizer-control-css' => 'cd37aa53', 4435 - 'aphront-tooltip-css' => 'cd37aa53', 4436 - 'aphront-typeahead-control-css' => 'cd37aa53', 4428 + 'aphront-dialog-view-css' => 'c98eaabf', 4429 + 'aphront-error-view-css' => 'c98eaabf', 4430 + 'aphront-list-filter-view-css' => 'c98eaabf', 4431 + 'aphront-pager-view-css' => 'c98eaabf', 4432 + 'aphront-panel-view-css' => 'c98eaabf', 4433 + 'aphront-table-view-css' => 'c98eaabf', 4434 + 'aphront-tokenizer-control-css' => 'c98eaabf', 4435 + 'aphront-tooltip-css' => 'c98eaabf', 4436 + 'aphront-typeahead-control-css' => 'c98eaabf', 4437 4437 'differential-changeset-view-css' => '4dc2311c', 4438 4438 'differential-core-view-css' => '4dc2311c', 4439 4439 'differential-inline-comment-editor' => '5e9e5c4e', ··· 4447 4447 'differential-table-of-contents-css' => '4dc2311c', 4448 4448 'diffusion-commit-view-css' => 'c8ce2d88', 4449 4449 'diffusion-icons-css' => 'c8ce2d88', 4450 - 'global-drag-and-drop-css' => 'cd37aa53', 4450 + 'global-drag-and-drop-css' => 'c98eaabf', 4451 4451 'inline-comment-summary-css' => '4dc2311c', 4452 4452 'javelin-aphlict' => '64eeda79', 4453 4453 'javelin-behavior' => '9564fa17', ··· 4522 4522 'javelin-util' => '9564fa17', 4523 4523 'javelin-vector' => '9564fa17', 4524 4524 'javelin-workflow' => '9564fa17', 4525 - 'lightbox-attachment-css' => 'cd37aa53', 4525 + 'lightbox-attachment-css' => 'c98eaabf', 4526 4526 'maniphest-task-summary-css' => '49898640', 4527 - 'phabricator-action-list-view-css' => 'cd37aa53', 4528 - 'phabricator-application-launch-view-css' => 'cd37aa53', 4527 + 'phabricator-action-list-view-css' => 'c98eaabf', 4528 + 'phabricator-application-launch-view-css' => 'c98eaabf', 4529 4529 'phabricator-busy' => '64eeda79', 4530 4530 'phabricator-content-source-view-css' => '4dc2311c', 4531 - 'phabricator-core-css' => 'cd37aa53', 4532 - 'phabricator-crumbs-view-css' => 'cd37aa53', 4531 + 'phabricator-core-css' => 'c98eaabf', 4532 + 'phabricator-crumbs-view-css' => 'c98eaabf', 4533 4533 'phabricator-drag-and-drop-file-upload' => '5e9e5c4e', 4534 4534 'phabricator-dropdown-menu' => '64eeda79', 4535 4535 'phabricator-file-upload' => '64eeda79', 4536 - 'phabricator-filetree-view-css' => 'cd37aa53', 4537 - 'phabricator-flag-css' => 'cd37aa53', 4536 + 'phabricator-filetree-view-css' => 'c98eaabf', 4537 + 'phabricator-flag-css' => 'c98eaabf', 4538 4538 'phabricator-hovercard' => '64eeda79', 4539 - 'phabricator-jump-nav' => 'cd37aa53', 4539 + 'phabricator-jump-nav' => 'c98eaabf', 4540 4540 'phabricator-keyboard-shortcut' => '64eeda79', 4541 4541 'phabricator-keyboard-shortcut-manager' => '64eeda79', 4542 - 'phabricator-main-menu-view' => 'cd37aa53', 4542 + 'phabricator-main-menu-view' => 'c98eaabf', 4543 4543 'phabricator-menu-item' => '64eeda79', 4544 - 'phabricator-nav-view-css' => 'cd37aa53', 4544 + 'phabricator-nav-view-css' => 'c98eaabf', 4545 4545 'phabricator-notification' => '64eeda79', 4546 - 'phabricator-notification-css' => 'cd37aa53', 4547 - 'phabricator-notification-menu-css' => 'cd37aa53', 4546 + 'phabricator-notification-css' => 'c98eaabf', 4547 + 'phabricator-notification-menu-css' => 'c98eaabf', 4548 4548 'phabricator-object-selector-css' => '4dc2311c', 4549 4549 'phabricator-phtize' => '64eeda79', 4550 4550 'phabricator-prefab' => '64eeda79', 4551 4551 'phabricator-project-tag-css' => '49898640', 4552 - 'phabricator-property-list-view-css' => 'cd37aa53', 4553 - 'phabricator-remarkup-css' => 'cd37aa53', 4552 + 'phabricator-property-list-view-css' => 'c98eaabf', 4553 + 'phabricator-remarkup-css' => 'c98eaabf', 4554 4554 'phabricator-shaped-request' => '5e9e5c4e', 4555 - 'phabricator-side-menu-view-css' => 'cd37aa53', 4556 - 'phabricator-standard-page-view' => 'cd37aa53', 4557 - 'phabricator-tag-view-css' => 'cd37aa53', 4555 + 'phabricator-side-menu-view-css' => 'c98eaabf', 4556 + 'phabricator-standard-page-view' => 'c98eaabf', 4557 + 'phabricator-tag-view-css' => 'c98eaabf', 4558 4558 'phabricator-textareautils' => '64eeda79', 4559 4559 'phabricator-tooltip' => '64eeda79', 4560 - 'phabricator-transaction-view-css' => 'cd37aa53', 4561 - 'phabricator-zindex-css' => 'cd37aa53', 4562 - 'phui-button-css' => 'cd37aa53', 4563 - 'phui-form-css' => 'cd37aa53', 4564 - 'phui-form-view-css' => 'cd37aa53', 4565 - 'phui-header-view-css' => 'cd37aa53', 4566 - 'phui-icon-view-css' => 'cd37aa53', 4567 - 'phui-list-view-css' => 'cd37aa53', 4568 - 'phui-object-item-list-view-css' => 'cd37aa53', 4569 - 'phui-spacing-css' => 'cd37aa53', 4570 - 'sprite-apps-large-css' => 'cd37aa53', 4571 - 'sprite-gradient-css' => 'cd37aa53', 4572 - 'sprite-icons-css' => 'cd37aa53', 4573 - 'sprite-menu-css' => 'cd37aa53', 4574 - 'sprite-status-css' => 'cd37aa53', 4575 - 'syntax-highlighting-css' => 'cd37aa53', 4560 + 'phabricator-transaction-view-css' => 'c98eaabf', 4561 + 'phabricator-zindex-css' => 'c98eaabf', 4562 + 'phui-button-css' => 'c98eaabf', 4563 + 'phui-form-css' => 'c98eaabf', 4564 + 'phui-form-view-css' => 'c98eaabf', 4565 + 'phui-header-view-css' => 'c98eaabf', 4566 + 'phui-icon-view-css' => 'c98eaabf', 4567 + 'phui-list-view-css' => 'c98eaabf', 4568 + 'phui-object-item-list-view-css' => 'c98eaabf', 4569 + 'phui-spacing-css' => 'c98eaabf', 4570 + 'sprite-apps-large-css' => 'c98eaabf', 4571 + 'sprite-gradient-css' => 'c98eaabf', 4572 + 'sprite-icons-css' => 'c98eaabf', 4573 + 'sprite-menu-css' => 'c98eaabf', 4574 + 'sprite-status-css' => 'c98eaabf', 4575 + 'syntax-highlighting-css' => 'c98eaabf', 4576 4576 ), 4577 4577 ));
+8 -2
src/__phutil_library_map__.php
··· 1472 1472 'PhabricatorPolicy' => 'applications/policy/filter/PhabricatorPolicy.php', 1473 1473 'PhabricatorPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorPolicyAwareQuery.php', 1474 1474 'PhabricatorPolicyAwareTestQuery' => 'applications/policy/__tests__/PhabricatorPolicyAwareTestQuery.php', 1475 - 'PhabricatorPolicyCapability' => 'applications/policy/constants/PhabricatorPolicyCapability.php', 1475 + 'PhabricatorPolicyCapability' => 'applications/policy/capability/PhabricatorPolicyCapability.php', 1476 + 'PhabricatorPolicyCapabilityCanEdit' => 'applications/policy/capability/PhabricatorPolicyCapabilityCanEdit.php', 1477 + 'PhabricatorPolicyCapabilityCanJoin' => 'applications/policy/capability/PhabricatorPolicyCapabilityCanJoin.php', 1478 + 'PhabricatorPolicyCapabilityCanView' => 'applications/policy/capability/PhabricatorPolicyCapabilityCanView.php', 1476 1479 'PhabricatorPolicyConfigOptions' => 'applications/policy/config/PhabricatorPolicyConfigOptions.php', 1477 1480 'PhabricatorPolicyConstants' => 'applications/policy/constants/PhabricatorPolicyConstants.php', 1478 1481 'PhabricatorPolicyController' => 'applications/policy/controller/PhabricatorPolicyController.php', ··· 3656 3659 'PhabricatorPolicies' => 'PhabricatorPolicyConstants', 3657 3660 'PhabricatorPolicyAwareQuery' => 'PhabricatorOffsetPagedQuery', 3658 3661 'PhabricatorPolicyAwareTestQuery' => 'PhabricatorPolicyAwareQuery', 3659 - 'PhabricatorPolicyCapability' => 'PhabricatorPolicyConstants', 3662 + 'PhabricatorPolicyCapability' => 'Phobject', 3663 + 'PhabricatorPolicyCapabilityCanEdit' => 'PhabricatorPolicyCapability', 3664 + 'PhabricatorPolicyCapabilityCanJoin' => 'PhabricatorPolicyCapability', 3665 + 'PhabricatorPolicyCapabilityCanView' => 'PhabricatorPolicyCapability', 3660 3666 'PhabricatorPolicyConfigOptions' => 'PhabricatorApplicationConfigOptions', 3661 3667 'PhabricatorPolicyController' => 'PhabricatorController', 3662 3668 'PhabricatorPolicyDataTestCase' => 'PhabricatorTestCase',
+16 -13
src/aphront/configuration/AphrontDefaultApplicationConfiguration.php
··· 172 172 $list = phutil_tag('ul', array(), $list); 173 173 } 174 174 175 - $content = phutil_tag( 176 - 'div', 177 - array( 178 - 'class' => 'aphront-policy-exception', 179 - ), 180 - array( 181 - $ex->getMessage(), 182 - $list, 183 - )); 175 + $content = array( 176 + phutil_tag( 177 + 'div', 178 + array( 179 + 'class' => 'aphront-policy-rejection', 180 + ), 181 + $ex->getRejection()), 182 + phutil_tag( 183 + 'div', 184 + array( 185 + 'class' => 'aphront-capability-details', 186 + ), 187 + pht('Users with the "%s" capability:', $ex->getCapabilityName())), 188 + $list, 189 + ); 184 190 185 191 $dialog = new AphrontDialogView(); 186 192 $dialog 187 - ->setTitle( 188 - $is_serious 189 - ? 'Access Denied' 190 - : "You Shall Not Pass") 193 + ->setTitle($ex->getTitle()) 191 194 ->setClass('aphront-access-dialog') 192 195 ->setUser($user) 193 196 ->appendChild($content);
+3 -5
src/applications/differential/storage/DifferentialRevision.php
··· 357 357 case PhabricatorPolicyCapability::CAN_VIEW: 358 358 $description[] = pht( 359 359 "A revision's reviewers can always view it."); 360 - if ($this->getRepositoryPHID()) { 361 - $description[] = pht( 362 - 'This revision belongs to a repository. Other users must be able '. 363 - 'to view the repository in order to view this revision.'); 364 - } 360 + $description[] = pht( 361 + 'If a revision belongs to a repository, other users must be able '. 362 + 'to view the repository in order to view the revision.'); 365 363 break; 366 364 } 367 365
+61
src/applications/policy/capability/PhabricatorPolicyCapability.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorPolicyCapability extends Phobject { 4 + 5 + const CAN_VIEW = 'view'; 6 + const CAN_EDIT = 'edit'; 7 + const CAN_JOIN = 'join'; 8 + 9 + /** 10 + * Get the unique key identifying this capability. This key must be globally 11 + * unique. Application capabilities should be namespaced. For example: 12 + * 13 + * application.create 14 + * 15 + * @return string Globally unique capability key. 16 + */ 17 + abstract public function getCapabilityKey(); 18 + 19 + 20 + /** 21 + * Return a human-readable descriptive name for this capability, like 22 + * "Can View". 23 + * 24 + * @return string Human-readable name describing the capability. 25 + */ 26 + abstract public function getCapabilityName(); 27 + 28 + 29 + /** 30 + * Return a human-readable string describing what not having this capability 31 + * prevents the user from doing. For example: 32 + * 33 + * - You do not have permission to edit this object. 34 + * - You do not have permission to create new tasks. 35 + * 36 + * @return string Human-readable name describing what failing a check for this 37 + * capability prevents the user from doing. 38 + */ 39 + abstract public function describeCapabilityRejection(); 40 + 41 + 42 + final public static function getCapabilityByKey($key) { 43 + return idx(self::getCapabilityMap(), $key); 44 + } 45 + 46 + final public static function getCapabilityMap() { 47 + static $map; 48 + if ($map === null) { 49 + $capabilities = id(new PhutilSymbolLoader()) 50 + ->setAncestorClass(__CLASS__) 51 + ->loadObjects(); 52 + 53 + $map = mpull($capabilities, null, 'getCapabilityKey'); 54 + } 55 + 56 + return $map; 57 + } 58 + 59 + } 60 + 61 +
+18
src/applications/policy/capability/PhabricatorPolicyCapabilityCanEdit.php
··· 1 + <?php 2 + 3 + final class PhabricatorPolicyCapabilityCanEdit 4 + extends PhabricatorPolicyCapability { 5 + 6 + public function getCapabilityKey() { 7 + return self::CAN_EDIT; 8 + } 9 + 10 + public function getCapabilityName() { 11 + return pht('Can Edit'); 12 + } 13 + 14 + public function describeCapabilityRejection() { 15 + return pht('You do not have permission to edit this object.'); 16 + } 17 + 18 + }
+18
src/applications/policy/capability/PhabricatorPolicyCapabilityCanJoin.php
··· 1 + <?php 2 + 3 + final class PhabricatorPolicyCapabilityCanJoin 4 + extends PhabricatorPolicyCapability { 5 + 6 + public function getCapabilityKey() { 7 + return self::CAN_JOIN; 8 + } 9 + 10 + public function getCapabilityName() { 11 + return pht('Can Join'); 12 + } 13 + 14 + public function describeCapabilityRejection() { 15 + return pht('You do not have permission to join this object.'); 16 + } 17 + 18 + }
+18
src/applications/policy/capability/PhabricatorPolicyCapabilityCanView.php
··· 1 + <?php 2 + 3 + final class PhabricatorPolicyCapabilityCanView 4 + extends PhabricatorPolicyCapability { 5 + 6 + public function getCapabilityKey() { 7 + return self::CAN_VIEW; 8 + } 9 + 10 + public function getCapabilityName() { 11 + return pht('Can View'); 12 + } 13 + 14 + public function describeCapabilityRejection() { 15 + return pht('You do not have permission to view this object.'); 16 + } 17 + 18 + }
-9
src/applications/policy/constants/PhabricatorPolicyCapability.php
··· 1 - <?php 2 - 3 - final class PhabricatorPolicyCapability extends PhabricatorPolicyConstants { 4 - 5 - const CAN_VIEW = 'view'; 6 - const CAN_EDIT = 'edit'; 7 - const CAN_JOIN = 'join'; 8 - 9 - }
+16 -3
src/applications/policy/controller/PhabricatorPolicyExplainController.php
··· 45 45 ->executeOne(); 46 46 $object_uri = $handle->getURI(); 47 47 48 - $explanation = $policy->getExplanation($capability); 48 + $explanation = PhabricatorPolicy::getPolicyExplanation( 49 + $viewer, 50 + $policy->getPHID()); 51 + 49 52 $auto_info = (array)$object->describeAutomaticCapability($capability); 53 + 54 + $auto_info = array_merge( 55 + array($explanation), 56 + $auto_info); 57 + $auto_info = array_filter($auto_info); 50 58 51 59 foreach ($auto_info as $key => $info) { 52 60 $auto_info[$key] = phutil_tag('li', array(), $info); ··· 56 64 } 57 65 58 66 $content = array( 59 - $explanation, 67 + pht('Users with the "%s" capability:', "Can View"), 60 68 $auto_info, 61 69 ); 62 70 71 + $object_name = pht( 72 + '%s %s', 73 + $handle->getTypeName(), 74 + $handle->getObjectName()); 75 + 63 76 $dialog = id(new AphrontDialogView()) 64 77 ->setUser($viewer) 65 78 ->setClass('aphront-access-dialog') 66 - ->setTitle(pht('Policy Details')) 79 + ->setTitle(pht('Policy Details: %s', $object_name)) 67 80 ->appendChild($content) 68 81 ->addCancelButton($object_uri, pht('Done')); 69 82
+30
src/applications/policy/exception/PhabricatorPolicyException.php
··· 2 2 3 3 final class PhabricatorPolicyException extends Exception { 4 4 5 + private $title; 6 + private $rejection; 7 + private $capabilityName; 5 8 private $moreInfo = array(); 9 + 10 + public function setTitle($title) { 11 + $this->title = $title; 12 + return $this; 13 + } 14 + 15 + public function getTitle() { 16 + return $this->title; 17 + } 18 + 19 + public function setCapabilityName($capability_name) { 20 + $this->capabilityName = $capability_name; 21 + return $this; 22 + } 23 + 24 + public function getCapabilityName() { 25 + return $this->capabilityName; 26 + } 27 + 28 + public function setRejection($rejection) { 29 + $this->rejection = $rejection; 30 + return $this; 31 + } 32 + 33 + public function getRejection() { 34 + return $this->rejection; 35 + } 6 36 7 37 public function setMoreInfo(array $more_info) { 8 38 $this->moreInfo = $more_info;
+30 -42
src/applications/policy/filter/PhabricatorPolicy.php
··· 130 130 return $this->getName(); 131 131 } 132 132 133 - public function getExplanation($capability) { 134 - switch ($capability) { 135 - case PhabricatorPolicyCapability::CAN_VIEW: 136 - switch ($this->getPHID()) { 137 - case PhabricatorPolicies::POLICY_PUBLIC: 138 - return pht('Visible to the entire internet.'); 139 - case PhabricatorPolicies::POLICY_USER: 140 - return pht('Visible to all logged in users.'); 141 - case PhabricatorPolicies::POLICY_ADMIN: 142 - return pht('Visible to all administrators.'); 143 - case PhabricatorPolicies::POLICY_NOONE: 144 - return pht('Not visible to anyone by default.'); 145 - } 133 + public static function getPolicyExplanation( 134 + PhabricatorUser $viewer, 135 + $policy) { 146 136 147 - switch ($this->getType()) { 148 - case PhabricatorPolicyType::TYPE_PROJECT: 149 - return pht( 150 - 'Visible to members of the project "%s".', 151 - $this->getName()); 152 - case PhabricatorPolicyType::TYPE_MASKED: 153 - return pht('Other: %s', $this->getName()); 154 - } 155 - break; 156 - case PhabricatorPolicyCapability::CAN_EDIT: 157 - switch ($this->getPHID()) { 158 - case PhabricatorPolicies::POLICY_USER: 159 - return pht('Editable by all logged in users.'); 160 - case PhabricatorPolicies::POLICY_ADMIN: 161 - return pht('Editable by all administrators.'); 162 - case PhabricatorPolicies::POLICY_NOONE: 163 - return pht('Not editable by default.'); 164 - } 137 + switch ($policy) { 138 + case PhabricatorPolicies::POLICY_PUBLIC: 139 + return pht('This object is public.'); 140 + case PhabricatorPolicies::POLICY_USER: 141 + return pht('Logged in users can take this action.'); 142 + case PhabricatorPolicies::POLICY_ADMIN: 143 + return pht('Administrators can take this action.'); 144 + case PhabricatorPolicies::POLICY_NOONE: 145 + return pht('By default, no one can take this action.'); 146 + default: 147 + $handle = id(new PhabricatorHandleQuery()) 148 + ->setViewer($viewer) 149 + ->withPHIDs(array($policy)) 150 + ->executeOne(); 165 151 166 - switch ($this->getType()) { 167 - case PhabricatorPolicyType::TYPE_PROJECT: 168 - return pht( 169 - 'Editable by members of the project "%s".', 170 - $this->getName()); 171 - case PhabricatorPolicyType::TYPE_MASKED: 172 - return pht('Other: %s', $this->getName()); 152 + $type = phid_get_type($policy); 153 + if ($type == PhabricatorProjectPHIDTypeProject::TYPECONST) { 154 + return pht( 155 + 'Members of the project "%s" can take this action.', 156 + $handle->getFullName()); 157 + } else if ($type == PhabricatorPeoplePHIDTypeUser::TYPECONST) { 158 + return pht( 159 + '%s can take this action.', 160 + $handle->getFullName()); 161 + } else { 162 + return pht( 163 + 'This object has an unknown or invalid policy setting ("%s").', 164 + $policy); 173 165 } 174 - break; 175 166 } 176 - 177 - 178 - return pht('?'); 179 167 } 180 168 181 169 public function getFullName() {
+56 -106
src/applications/policy/filter/PhabricatorPolicyFilter.php
··· 242 242 return; 243 243 } 244 244 245 - $more = array(); 246 - switch ($capability) { 247 - case PhabricatorPolicyCapability::CAN_VIEW: 248 - $message = pht( 249 - 'This object exists, but you do not have permission to view it.'); 250 - break; 251 - case PhabricatorPolicyCapability::CAN_EDIT: 252 - $message = pht('You do not have permission to edit this object.'); 253 - break; 254 - case PhabricatorPolicyCapability::CAN_JOIN: 255 - $message = pht('You do not have permission to join this object.'); 256 - break; 257 - default: 258 - // TODO: Farm these out to applications? 259 - $message = pht( 260 - 'You do not have a required capability ("%s") to do whatever you '. 261 - 'are trying to do.', 262 - $capability); 263 - break; 245 + $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability); 246 + if ($capobj) { 247 + $rejection = $capobj->describeCapabilityRejection(); 248 + $capability_name = $capobj->getCapabilityName(); 249 + } else { 250 + $rejection = pht( 251 + 'You do not have the required capability ("%s") to do whatever you '. 252 + 'are trying to do.', 253 + $capability); 254 + $capability_name = $capability; 264 255 } 265 256 266 - switch ($policy) { 267 - case PhabricatorPolicies::POLICY_PUBLIC: 268 - // Presumably, this is a bug, so we don't bother specializing the 269 - // strings. 270 - $more = pht('This object is public.'); 271 - break; 272 - case PhabricatorPolicies::POLICY_USER: 273 - // We always raise this as "log in", so we don't need to specialize. 274 - $more = pht('This object is available to logged in users.'); 275 - break; 276 - case PhabricatorPolicies::POLICY_ADMIN: 277 - switch ($capability) { 278 - case PhabricatorPolicyCapability::CAN_VIEW: 279 - $more = pht('Administrators can view this object.'); 280 - break; 281 - case PhabricatorPolicyCapability::CAN_EDIT: 282 - $more = pht('Administrators can edit this object.'); 283 - break; 284 - case PhabricatorPolicyCapability::CAN_JOIN: 285 - $more = pht('Administrators can join this object.'); 286 - break; 287 - } 288 - break; 289 - case PhabricatorPolicies::POLICY_NOONE: 290 - switch ($capability) { 291 - case PhabricatorPolicyCapability::CAN_VIEW: 292 - $more = pht('By default, no one can view this object.'); 293 - break; 294 - case PhabricatorPolicyCapability::CAN_EDIT: 295 - $more = pht('By default, no one can edit this object.'); 296 - break; 297 - case PhabricatorPolicyCapability::CAN_JOIN: 298 - $more = pht('By default, no one can join this object.'); 299 - break; 300 - } 301 - break; 302 - default: 303 - $handle = id(new PhabricatorHandleQuery()) 304 - ->setViewer($this->viewer) 305 - ->withPHIDs(array($policy)) 306 - ->executeOne(); 257 + $more = PhabricatorPolicy::getPolicyExplanation($this->viewer, $policy); 258 + $exceptions = $object->describeAutomaticCapability($capability); 307 259 308 - $type = phid_get_type($policy); 309 - if ($type == PhabricatorProjectPHIDTypeProject::TYPECONST) { 310 - switch ($capability) { 311 - case PhabricatorPolicyCapability::CAN_VIEW: 312 - $more = pht( 313 - 'This object is visible to members of the project "%s".', 314 - $handle->getFullName()); 315 - break; 316 - case PhabricatorPolicyCapability::CAN_EDIT: 317 - $more = pht( 318 - 'This object can be edited by members of the project "%s".', 319 - $handle->getFullName()); 320 - break; 321 - case PhabricatorPolicyCapability::CAN_JOIN: 322 - $more = pht( 323 - 'This object can be joined by members of the project "%s".', 324 - $handle->getFullName()); 325 - break; 326 - } 327 - } else if ($type == PhabricatorPeoplePHIDTypeUser::TYPECONST) { 328 - switch ($capability) { 329 - case PhabricatorPolicyCapability::CAN_VIEW: 330 - $more = pht( 331 - '%s can view this object.', 332 - $handle->getFullName()); 333 - break; 334 - case PhabricatorPolicyCapability::CAN_EDIT: 335 - $more = pht( 336 - '%s can edit this object.', 337 - $handle->getFullName()); 338 - break; 339 - case PhabricatorPolicyCapability::CAN_JOIN: 340 - $more = pht( 341 - '%s can join this object.', 342 - $handle->getFullName()); 343 - break; 344 - } 345 - } else { 346 - $more = pht("This object has an unknown or invalid policy setting."); 347 - } 348 - break; 260 + $details = array_filter(array_merge(array($more), (array)$exceptions)); 261 + 262 + // NOTE: Not every policy object has a PHID, just pull an arbitrary 263 + // "unknown object" handle if this fails. We're just using this to provide 264 + // a better error message if we can. 265 + 266 + $phid = '?'; 267 + if ($object instanceof PhabricatorLiskDAO) { 268 + try { 269 + $phid = $object->getPHID(); 270 + } catch (Exception $ignored) { 271 + // Ignore. 272 + } 273 + } 274 + 275 + $handle = id(new PhabricatorHandleQuery()) 276 + ->setViewer($this->viewer) 277 + ->withPHIDs(array($phid)) 278 + ->executeOne(); 279 + $object_name = pht( 280 + '%s %s', 281 + $handle->getTypeName(), 282 + $handle->getObjectName()); 283 + 284 + $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); 285 + if ($is_serious) { 286 + $title = pht( 287 + 'Access Denied: %s', 288 + $object_name); 289 + } else { 290 + $title = pht( 291 + 'You Shall Not Pass: %s', 292 + $object_name); 349 293 } 350 294 351 - $more = array_merge( 352 - array_filter(array($more)), 353 - array_filter((array)$object->describeAutomaticCapability($capability))); 295 + $full_message = pht( 296 + '[%s] (%s) %s // %s', 297 + $title, 298 + $capability_name, 299 + $rejection, 300 + implode(' ', $details)); 354 301 355 - $exception = new PhabricatorPolicyException($message); 356 - $exception->setMoreInfo($more); 302 + $exception = id(new PhabricatorPolicyException($full_message)) 303 + ->setTitle($title) 304 + ->setRejection($rejection) 305 + ->setCapabilityName($capability_name) 306 + ->setMoreInfo($details); 357 307 358 308 throw $exception; 359 309 }
+2 -1
src/applications/policy/management/PhabricatorPolicyManagementShowWorkflow.php
··· 64 64 foreach ($policies as $capability => $policy) { 65 65 $console->writeOut(" **%s**\n", $capability); 66 66 $console->writeOut(" %s\n", $policy->renderDescription()); 67 - $console->writeOut(" %s\n", $policy->getExplanation($capability)); 67 + $console->writeOut(" %s\n", 68 + PhabricatorPolicy::getPolicyExplanation($viewer, $policy->getPHID())); 68 69 $console->writeOut("\n"); 69 70 70 71 $more = (array)$object->describeAutomaticCapability($capability);
+8
webroot/rsrc/css/aphront/dialog-view.css
··· 116 116 margin: 12px 24px; 117 117 list-style: circle; 118 118 } 119 + 120 + .aphront-policy-rejection { 121 + font-weight: bold; 122 + } 123 + 124 + .aphront-capability-details { 125 + margin: 20px 0 4px; 126 + }