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

Require several advanced postgraduate degrees to understand object policies

Summary:
Fixes T11836. See some prior discussion in T8376#120613.

The policy hint in headers in the UI is not exhaustive, and can not reasonably be exhaustive. For example, on a revision, it may say "All Users", but really mean "All users who can see the space this object is in and the repository it belongs to, plus the revision author and reviewers".

These rules are explained if you click (and, often, in the documentation), but "All Users" is still at least somewhat misleading.

I don't think there's any perfect solution here that balances the needs of both new and experienced users perfectly, but this change tries to do a bit better about avoiding cases where we say something very open (like "All Users") when the real policy is not very open.

Specifically, I've made these changes to the header:

- Spaces are now listed in the tag, so it will say `(S3 > All Users)` instead of `(All Users)`. They're already listed in the header, this just makes it more explicit that Spaces are a policy container and part of the view policy.
- Extended policy objects are now listed in the tag, so it will say `(S3 > rARC > All Users)` for a revision in the Arcanist repository which is also in Space 3.
- Objects can now provide a "Policy Codex", which is an object that represents a rulebook of more sophisticated policy descriptions. This codex can replace the tag with something else.
- Imported calendar events now say "Uses Import Policy" instead of, e.g., "All Users".

I've made these changes to the policy dialog:

- Split it into more visually separate sections.
- Added an explicit section for extended policies ("You must also have access to these other objects: ...").
- Broken the object policy rules into a "Special Rules" section (for rules like "you can only see a revision if you can see the repository it is part of") and an "Object Policy" section (for the actual object policy).
- Tried to make it a little more readable?
- The new policy dialogs are great to curl up with in front of a fire with a nice cup of cocoa.

I've made these changes to infrastructure:

- Implementing `PhabricatorPolicyInterface` no longer requires you to implement `describeAutomaticCapability()`.
- Instead, implement `PhabricatorPolicyCodexInterface` and return a `PhabricatorPolicyCodex` object.
- This "codex" is a policy rulebook which can set all the policy icons, labels, colors, rules, etc., to properly explain complex policies.
- Broadly, the old method was usually either not useful (most objects have no special rules) or not powerful enough (objects with special rules often need to do more in order to explain them).

Test Plan:
{F1912860}

{F1912861}

{F1912862}

{F1912863}

Reviewers: chad

Reviewed By: chad

Subscribers: avivey

Maniphest Tasks: T11836

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

+721 -128
+10
src/__phutil_library_map__.php
··· 1668 1668 'PHUIPagerView' => 'view/phui/PHUIPagerView.php', 1669 1669 'PHUIPinboardItemView' => 'view/phui/PHUIPinboardItemView.php', 1670 1670 'PHUIPinboardView' => 'view/phui/PHUIPinboardView.php', 1671 + 'PHUIPolicySectionView' => 'applications/policy/view/PHUIPolicySectionView.php', 1671 1672 'PHUIPropertyGroupView' => 'view/phui/PHUIPropertyGroupView.php', 1672 1673 'PHUIPropertyListExample' => 'applications/uiexample/examples/PHUIPropertyListExample.php', 1673 1674 'PHUIPropertyListView' => 'view/phui/PHUIPropertyListView.php', ··· 2071 2072 'PhabricatorCalendarEventNameTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventNameTransaction.php', 2072 2073 'PhabricatorCalendarEventNotificationView' => 'applications/calendar/notifications/PhabricatorCalendarEventNotificationView.php', 2073 2074 'PhabricatorCalendarEventPHIDType' => 'applications/calendar/phid/PhabricatorCalendarEventPHIDType.php', 2075 + 'PhabricatorCalendarEventPolicyCodex' => 'applications/calendar/codex/PhabricatorCalendarEventPolicyCodex.php', 2074 2076 'PhabricatorCalendarEventQuery' => 'applications/calendar/query/PhabricatorCalendarEventQuery.php', 2075 2077 'PhabricatorCalendarEventRSVPEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventRSVPEmailCommand.php', 2076 2078 'PhabricatorCalendarEventRecurringTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventRecurringTransaction.php', ··· 3311 3313 'PhabricatorPolicyCanViewCapability' => 'applications/policy/capability/PhabricatorPolicyCanViewCapability.php', 3312 3314 'PhabricatorPolicyCapability' => 'applications/policy/capability/PhabricatorPolicyCapability.php', 3313 3315 'PhabricatorPolicyCapabilityTestCase' => 'applications/policy/capability/__tests__/PhabricatorPolicyCapabilityTestCase.php', 3316 + 'PhabricatorPolicyCodex' => 'applications/policy/codex/PhabricatorPolicyCodex.php', 3317 + 'PhabricatorPolicyCodexInterface' => 'applications/policy/codex/PhabricatorPolicyCodexInterface.php', 3318 + 'PhabricatorPolicyCodexRuleDescription' => 'applications/policy/codex/PhabricatorPolicyCodexRuleDescription.php', 3314 3319 'PhabricatorPolicyConfigOptions' => 'applications/policy/config/PhabricatorPolicyConfigOptions.php', 3315 3320 'PhabricatorPolicyConstants' => 'applications/policy/constants/PhabricatorPolicyConstants.php', 3316 3321 'PhabricatorPolicyController' => 'applications/policy/controller/PhabricatorPolicyController.php', ··· 6447 6452 'PHUIPagerView' => 'AphrontView', 6448 6453 'PHUIPinboardItemView' => 'AphrontView', 6449 6454 'PHUIPinboardView' => 'AphrontView', 6455 + 'PHUIPolicySectionView' => 'AphrontTagView', 6450 6456 'PHUIPropertyGroupView' => 'AphrontTagView', 6451 6457 'PHUIPropertyListExample' => 'PhabricatorUIExample', 6452 6458 'PHUIPropertyListView' => 'AphrontView', ··· 6870 6876 'PhabricatorCalendarDAO', 6871 6877 'PhabricatorPolicyInterface', 6872 6878 'PhabricatorExtendedPolicyInterface', 6879 + 'PhabricatorPolicyCodexInterface', 6873 6880 'PhabricatorProjectInterface', 6874 6881 'PhabricatorMarkupInterface', 6875 6882 'PhabricatorApplicationTransactionInterface', ··· 6923 6930 'PhabricatorCalendarEventNameTransaction' => 'PhabricatorCalendarEventTransactionType', 6924 6931 'PhabricatorCalendarEventNotificationView' => 'Phobject', 6925 6932 'PhabricatorCalendarEventPHIDType' => 'PhabricatorPHIDType', 6933 + 'PhabricatorCalendarEventPolicyCodex' => 'PhabricatorPolicyCodex', 6926 6934 'PhabricatorCalendarEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 6927 6935 'PhabricatorCalendarEventRSVPEmailCommand' => 'PhabricatorCalendarEventEmailCommand', 6928 6936 'PhabricatorCalendarEventRecurringTransaction' => 'PhabricatorCalendarEventTransactionType', ··· 8361 8369 'PhabricatorPolicyCanViewCapability' => 'PhabricatorPolicyCapability', 8362 8370 'PhabricatorPolicyCapability' => 'Phobject', 8363 8371 'PhabricatorPolicyCapabilityTestCase' => 'PhabricatorTestCase', 8372 + 'PhabricatorPolicyCodex' => 'Phobject', 8373 + 'PhabricatorPolicyCodexRuleDescription' => 'Phobject', 8364 8374 'PhabricatorPolicyConfigOptions' => 'PhabricatorApplicationConfigOptions', 8365 8375 'PhabricatorPolicyConstants' => 'Phobject', 8366 8376 'PhabricatorPolicyController' => 'PhabricatorController',
+80
src/applications/calendar/codex/PhabricatorCalendarEventPolicyCodex.php
··· 1 + <?php 2 + 3 + final class PhabricatorCalendarEventPolicyCodex 4 + extends PhabricatorPolicyCodex { 5 + 6 + public function getPolicyShortName() { 7 + $object = $this->getObject(); 8 + 9 + if (!$object->isImportedEvent()) { 10 + return null; 11 + } 12 + 13 + return pht('Uses Import Policy'); 14 + } 15 + 16 + public function getPolicyIcon() { 17 + $object = $this->getObject(); 18 + 19 + if (!$object->isImportedEvent()) { 20 + return null; 21 + } 22 + 23 + return 'fa-download'; 24 + } 25 + 26 + public function getPolicyTagClasses() { 27 + $object = $this->getObject(); 28 + 29 + if (!$object->isImportedEvent()) { 30 + return array(); 31 + } 32 + 33 + return array( 34 + 'policy-adjusted-special', 35 + ); 36 + } 37 + 38 + public function getPolicySpecialRuleDescriptions() { 39 + $object = $this->getObject(); 40 + 41 + $rules = array(); 42 + $rules[] = $this->newRule() 43 + ->setDescription( 44 + pht('The host of an event can always view and edit it.')); 45 + 46 + $rules[] = $this->newRule() 47 + ->setCapabilities( 48 + array( 49 + PhabricatorPolicyCapability::CAN_VIEW, 50 + )) 51 + ->setDescription( 52 + pht('Users who are invited to an event can always view it.')); 53 + 54 + 55 + $rules[] = $this->newRule() 56 + ->setCapabilities( 57 + array( 58 + PhabricatorPolicyCapability::CAN_VIEW, 59 + )) 60 + ->setIsActive($object->isImportedEvent()) 61 + ->setDescription( 62 + pht( 63 + 'Imported events can only be viewed by users who can view '. 64 + 'the import source.')); 65 + 66 + $rules[] = $this->newRule() 67 + ->setCapabilities( 68 + array( 69 + PhabricatorPolicyCapability::CAN_EDIT, 70 + )) 71 + ->setIsActive($object->isImportedEvent()) 72 + ->setDescription( 73 + pht( 74 + 'Imported events can not be edited in Phabricator.')); 75 + 76 + return $rules; 77 + } 78 + 79 + 80 + }
+7 -12
src/applications/calendar/storage/PhabricatorCalendarEvent.php
··· 4 4 implements 5 5 PhabricatorPolicyInterface, 6 6 PhabricatorExtendedPolicyInterface, 7 + PhabricatorPolicyCodexInterface, 7 8 PhabricatorProjectInterface, 8 9 PhabricatorMarkupInterface, 9 10 PhabricatorApplicationTransactionInterface, ··· 1217 1218 return false; 1218 1219 } 1219 1220 1220 - public function describeAutomaticCapability($capability) { 1221 - if ($this->isImportedEvent()) { 1222 - return pht( 1223 - 'Events imported from external sources can not be edited in '. 1224 - 'Phabricator.'); 1225 - } 1226 - 1227 - return pht( 1228 - 'The host of an event can always view and edit it. Users who are '. 1229 - 'invited to an event can always view it.'); 1230 - } 1231 - 1232 1221 1233 1222 /* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ 1234 1223 ··· 1249 1238 } 1250 1239 1251 1240 return $extended; 1241 + } 1242 + 1243 + /* -( PhabricatorPolicyCodexInterface )------------------------------------ */ 1244 + 1245 + public function newPolicyCodex() { 1246 + return new PhabricatorCalendarEventPolicyCodex(); 1252 1247 } 1253 1248 1254 1249
-8
src/applications/differential/storage/DifferentialRevision.php
··· 370 370 371 371 switch ($capability) { 372 372 case PhabricatorPolicyCapability::CAN_VIEW: 373 - // NOTE: In Differential, an automatic capability on a revision (being 374 - // an author) is sufficient to view it, even if you can not see the 375 - // repository the revision belongs to. We can bail out early in this 376 - // case. 377 - if ($this->hasAutomaticCapability($capability, $viewer)) { 378 - break; 379 - } 380 - 381 373 $repository_phid = $this->getRepositoryPHID(); 382 374 $repository = $this->getRepository(); 383 375
+106
src/applications/policy/codex/PhabricatorPolicyCodex.php
··· 1 + <?php 2 + 3 + /** 4 + * Rendering extensions that allows an object to render custom strings, 5 + * descriptions and explanations for the policy system to help users 6 + * understand complex policies. 7 + */ 8 + abstract class PhabricatorPolicyCodex 9 + extends Phobject { 10 + 11 + private $viewer; 12 + private $object; 13 + private $policy; 14 + private $capability; 15 + 16 + public function getPolicyShortName() { 17 + return null; 18 + } 19 + 20 + public function getPolicyIcon() { 21 + return null; 22 + } 23 + 24 + public function getPolicyTagClasses() { 25 + return array(); 26 + } 27 + 28 + public function getPolicySpecialRuleDescriptions() { 29 + return array(); 30 + } 31 + 32 + final protected function newRule() { 33 + return new PhabricatorPolicyCodexRuleDescription(); 34 + } 35 + 36 + final public function setViewer(PhabricatorUser $viewer) { 37 + $this->viewer = $viewer; 38 + return $this; 39 + } 40 + 41 + final public function getViewer() { 42 + return $this->viewer; 43 + } 44 + 45 + final public function setObject(PhabricatorPolicyCodexInterface $object) { 46 + $this->object = $object; 47 + return $this; 48 + } 49 + 50 + final public function getObject() { 51 + return $this->object; 52 + } 53 + 54 + final public function setCapability($capability) { 55 + $this->capability = $capability; 56 + return $this; 57 + } 58 + 59 + final public function getCapability() { 60 + return $this->capability; 61 + } 62 + 63 + final public function setPolicy(PhabricatorPolicy $policy) { 64 + $this->policy = $policy; 65 + return $this; 66 + } 67 + 68 + final public function getPolicy() { 69 + return $this->policy; 70 + } 71 + 72 + final public static function newFromObject( 73 + PhabricatorPolicyCodexInterface $object, 74 + PhabricatorUser $viewer) { 75 + 76 + if (!($object instanceof PhabricatorPolicyInterface)) { 77 + throw new Exception( 78 + pht( 79 + 'Object (of class "%s") implements interface "%s", but must also '. 80 + 'implement interface "%s".', 81 + get_class($object), 82 + 'PhabricatorPolicyCodexInterface', 83 + 'PhabricatorPolicyInterface')); 84 + } 85 + 86 + $codex = $object->newPolicyCodex(); 87 + if (!($codex instanceof PhabricatorPolicyCodex)) { 88 + throw new Exception( 89 + pht( 90 + 'Object (of class "%s") implements interface "%s", but defines '. 91 + 'method "%s" incorrectly: this method must return an object of '. 92 + 'class "%s".', 93 + get_class($object), 94 + 'PhabricatorPolicyCodexInterface', 95 + 'newPolicyCodex()', 96 + __CLASS__)); 97 + } 98 + 99 + $codex 100 + ->setObject($object) 101 + ->setViewer($viewer); 102 + 103 + return $codex; 104 + } 105 + 106 + }
+18
src/applications/policy/codex/PhabricatorPolicyCodexInterface.php
··· 1 + <?php 2 + 3 + interface PhabricatorPolicyCodexInterface { 4 + 5 + public function newPolicyCodex(); 6 + 7 + } 8 + 9 + // TEMPLATE IMPLEMENTATION ///////////////////////////////////////////////////// 10 + 11 + /* -( PhabricatorPolicyCodexInterface )------------------------------------ */ 12 + /* 13 + 14 + public function newPolicyCodex() { 15 + return new <<...>>PolicyCodex(); 16 + } 17 + 18 + */
+37
src/applications/policy/codex/PhabricatorPolicyCodexRuleDescription.php
··· 1 + <?php 2 + 3 + final class PhabricatorPolicyCodexRuleDescription 4 + extends Phobject { 5 + 6 + private $description; 7 + private $capabilities = array(); 8 + private $isActive = true; 9 + 10 + public function setDescription($description) { 11 + $this->description = $description; 12 + return $this; 13 + } 14 + 15 + public function getDescription() { 16 + return $this->description; 17 + } 18 + 19 + public function setCapabilities(array $capabilities) { 20 + $this->capabilities = $capabilities; 21 + return $this; 22 + } 23 + 24 + public function getCapabilities() { 25 + return $this->capabilities; 26 + } 27 + 28 + public function setIsActive($is_active) { 29 + $this->isActive = $is_active; 30 + return $this; 31 + } 32 + 33 + public function getIsActive() { 34 + return $this->isActive; 35 + } 36 + 37 + }
+218 -73
src/applications/policy/controller/PhabricatorPolicyExplainController.php
··· 34 34 ->setViewer($viewer) 35 35 ->withPHIDs(array($phid)) 36 36 ->executeOne(); 37 - $object_uri = nonempty($handle->getURI(), '/'); 38 - 39 - $explanation = PhabricatorPolicy::getPolicyExplanation( 40 - $viewer, 41 - $policy->getPHID()); 42 37 43 - $auto_info = (array)$object->describeAutomaticCapability($capability); 44 - 45 - $auto_info = array_merge( 46 - array($explanation), 47 - $auto_info); 48 - $auto_info = array_filter($auto_info); 49 - 50 - $capability_name = $capability; 51 - $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability); 52 - if ($capobj) { 53 - $capability_name = $capobj->getCapabilityName(); 54 - } 38 + $object_name = $handle->getName(); 39 + $object_uri = nonempty($handle->getURI(), '/'); 55 40 56 41 $dialog = id(new AphrontDialogView()) 57 42 ->setUser($viewer) 58 - ->setClass('aphront-access-dialog'); 43 + ->setClass('aphront-access-dialog') 44 + ->setTitle(pht('Policy Details: %s', $object_name)) 45 + ->addCancelButton($object_uri, pht('Done')); 59 46 60 - $this->appendSpaceInformation($dialog, $object, $policy, $capability); 47 + $space_section = $this->buildSpaceSection( 48 + $object, 49 + $policy, 50 + $capability); 61 51 62 - $intro = pht( 63 - 'Users with the "%s" capability for this object:', 64 - $capability_name); 52 + $extended_section = $this->buildExtendedSection( 53 + $object, 54 + $capability); 65 55 66 - $object_name = pht( 67 - '%s %s', 68 - $handle->getTypeName(), 69 - $handle->getObjectName()); 56 + $exceptions_section = $this->buildExceptionsSection( 57 + $object, 58 + $capability); 70 59 71 - $dialog 72 - ->setTitle(pht('Policy Details: %s', $object_name)) 73 - ->appendParagraph($intro) 74 - ->addCancelButton($object_uri, pht('Done')); 60 + $object_section = $this->buildObjectSection( 61 + $object, 62 + $policy, 63 + $capability, 64 + $handle); 75 65 76 - if ($auto_info) { 77 - $dialog->appendList($auto_info); 78 - } 66 + $dialog->appendChild( 67 + array( 68 + $space_section, 69 + $extended_section, 70 + $exceptions_section, 71 + $object_section, 72 + )); 79 73 80 - $this->appendStrengthInformation($dialog, $object, $policy, $capability); 81 74 82 75 return $dialog; 83 76 } 84 77 85 - private function appendSpaceInformation( 86 - AphrontDialogView $dialog, 78 + private function buildSpaceSection( 87 79 PhabricatorPolicyInterface $object, 88 80 PhabricatorPolicy $policy, 89 81 $capability) { 90 82 $viewer = $this->getViewer(); 91 83 92 84 if (!($object instanceof PhabricatorSpacesInterface)) { 93 - return; 85 + return null; 94 86 } 95 87 96 88 if (!PhabricatorSpacesNamespaceQuery::getSpacesExist($viewer)) { 97 - return; 89 + return null; 98 90 } 99 91 100 - // NOTE: We're intentionally letting users through here, even if they only 101 - // have access to one space. The intent is to help users in "space jail" 102 - // understand who objects they create are visible to: 103 - 104 92 $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID( 105 93 $object); 106 94 107 - $handles = $viewer->loadHandles(array($space_phid)); 108 - $doc_href = PhabricatorEnv::getDoclink('Spaces User Guide'); 109 - 110 - $dialog->appendParagraph( 111 - array( 112 - pht( 113 - 'This object is in %s, and can only be seen or edited by users with '. 114 - 'access to view objects in the space.', 115 - $handles[$space_phid]->renderLink()), 116 - ' ', 117 - phutil_tag( 118 - 'strong', 119 - array(), 120 - phutil_tag( 121 - 'a', 122 - array( 123 - 'href' => $doc_href, 124 - 'target' => '_blank', 125 - ), 126 - pht('Learn More'))), 127 - )); 128 - 129 95 $spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($viewer); 130 96 $space = idx($spaces, $space_phid); 131 97 if (!$space) { 132 - return; 98 + return null; 133 99 } 134 100 135 101 $space_policies = PhabricatorPolicyQuery::loadPolicies($viewer, $space); 136 102 $space_policy = idx($space_policies, PhabricatorPolicyCapability::CAN_VIEW); 137 103 if (!$space_policy) { 138 - return; 104 + return null; 139 105 } 140 106 107 + $doc_href = PhabricatorEnv::getDoclink('Spaces User Guide'); 108 + $capability_name = $this->getCapabilityName($capability); 109 + 110 + $space_section = id(new PHUIPolicySectionView()) 111 + ->setViewer($viewer) 112 + ->setIcon('fa-th-large bluegrey') 113 + ->setHeader(pht('Space')) 114 + ->setDocumentationLink(pht('Spaces Documentation'), $doc_href) 115 + ->appendList( 116 + array( 117 + array( 118 + phutil_tag('strong', array(), pht('Space:')), 119 + ' ', 120 + $viewer->renderHandle($space_phid)->setAsTag(true), 121 + ), 122 + array( 123 + phutil_tag('strong', array(), pht('%s:', $capability_name)), 124 + ' ', 125 + $space_policy->getShortName(), 126 + ), 127 + )) 128 + ->appendParagraph( 129 + pht( 130 + 'This object is in %s and can only be seen or edited by users '. 131 + 'with access to view objects in the space.', 132 + $viewer->renderHandle($space_phid))); 133 + 141 134 $space_explanation = PhabricatorPolicy::getPolicyExplanation( 142 135 $viewer, 143 136 $space_policy->getPHID()); 144 137 $items = array(); 145 138 $items[] = $space_explanation; 146 139 147 - $dialog->appendParagraph(pht('Users who can see objects in this space:')); 148 - $dialog->appendList($items); 140 + $space_section 141 + ->appendParagraph(pht('Users who can see objects in this space:')) 142 + ->appendList($items); 149 143 150 144 $view_capability = PhabricatorPolicyCapability::CAN_VIEW; 151 145 if ($capability == $view_capability) { 152 146 $stronger = $space_policy->isStrongerThan($policy); 153 147 if ($stronger) { 154 - $dialog->appendParagraph( 148 + $space_section->appendHint( 155 149 pht( 156 150 'The space this object is in has a more restrictive view '. 157 151 'policy ("%s") than the object does ("%s"), so the space\'s '. ··· 161 155 } 162 156 } 163 157 164 - $dialog->appendParagraph( 158 + $space_section->appendHint( 165 159 pht( 166 160 'After a user passes space policy checks, they must still pass '. 167 161 'object policy checks.')); 162 + 163 + return $space_section; 168 164 } 169 165 170 - private function appendStrengthInformation( 171 - AphrontDialogView $dialog, 166 + private function getStrengthInformation( 172 167 PhabricatorPolicyInterface $object, 173 168 PhabricatorPolicy $policy, 174 169 $capability) { ··· 206 201 $default_policy->getShortName()); 207 202 } 208 203 209 - $dialog->appendParagraph($info); 204 + return $info; 205 + } 206 + 207 + private function getCapabilityName($capability) { 208 + $capability_name = $capability; 209 + $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability); 210 + if ($capobj) { 211 + $capability_name = $capobj->getCapabilityName(); 212 + } 213 + 214 + return $capability_name; 215 + } 216 + 217 + private function buildExtendedSection( 218 + PhabricatorPolicyInterface $object, 219 + $capability) { 220 + $viewer = $this->getViewer(); 221 + 222 + if (!($object instanceof PhabricatorExtendedPolicyInterface)) { 223 + return null; 224 + } 225 + 226 + $extended_rules = $object->getExtendedPolicy($capability, $viewer); 227 + if (!$extended_rules) { 228 + return null; 229 + } 230 + 231 + $items = array(); 232 + foreach ($extended_rules as $extended_rule) { 233 + $extended_target = $extended_rule[0]; 234 + $extended_capabilities = (array)$extended_rule[1]; 235 + if (is_object($extended_target)) { 236 + $extended_target = $extended_target->getPHID(); 237 + } 238 + 239 + foreach ($extended_capabilities as $extended_capability) { 240 + $ex_name = $this->getCapabilityName($extended_capability); 241 + $items[] = array( 242 + phutil_tag('strong', array(), pht('%s:', $ex_name)), 243 + ' ', 244 + $viewer->renderHandle($extended_target)->setAsTag(true), 245 + ); 246 + } 247 + } 248 + 249 + return id(new PHUIPolicySectionView()) 250 + ->setViewer($viewer) 251 + ->setIcon('fa-link') 252 + ->setHeader(pht('Required Capabilities on Other Objects')) 253 + ->appendParagraph( 254 + pht( 255 + 'To access this object, users must have first have access '. 256 + 'capabilties on these other objects:')) 257 + ->appendList($items); 258 + } 259 + 260 + private function buildExceptionsSection( 261 + PhabricatorPolicyInterface $object, 262 + $capability) { 263 + $viewer = $this->getViewer(); 264 + 265 + if ($object instanceof PhabricatorPolicyCodexInterface) { 266 + $codex = PhabricatorPolicyCodex::newFromObject($object, $viewer); 267 + $rules = $codex->getPolicySpecialRuleDescriptions(); 268 + 269 + $exceptions = array(); 270 + foreach ($rules as $rule) { 271 + $is_active = $rule->getIsActive(); 272 + if ($is_active) { 273 + $rule_capabilities = $rule->getCapabilities(); 274 + if ($rule_capabilities) { 275 + if (!in_array($capability, $rule_capabilities)) { 276 + $is_active = false; 277 + } 278 + } 279 + } 280 + 281 + $description = $rule->getDescription(); 282 + 283 + if (!$is_active) { 284 + $description = phutil_tag( 285 + 'span', 286 + array( 287 + 'class' => 'phui-policy-section-view-inactive-rule', 288 + ), 289 + $description); 290 + } 291 + 292 + $exceptions[] = $description; 293 + } 294 + } else if (method_exists($object, 'describeAutomaticCapability')) { 295 + $exceptions = (array)$object->describeAutomaticCapability($capability); 296 + $exceptions = array_filter($exceptions); 297 + } else { 298 + $exceptions = array(); 299 + } 300 + 301 + if (!$exceptions) { 302 + return null; 303 + } 304 + 305 + 306 + return id(new PHUIPolicySectionView()) 307 + ->setViewer($viewer) 308 + ->setIcon('fa-unlock-alt red') 309 + ->setHeader(pht('Special Rules')) 310 + ->appendParagraph( 311 + pht( 312 + 'This object has special rules which override normal object '. 313 + 'policy rules:')) 314 + ->appendList($exceptions); 315 + } 316 + 317 + private function buildObjectSection( 318 + PhabricatorPolicyInterface $object, 319 + PhabricatorPolicy $policy, 320 + $capability, 321 + PhabricatorObjectHandle $handle) { 322 + 323 + $viewer = $this->getViewer(); 324 + $capability_name = $this->getCapabilityName($capability); 325 + 326 + $object_section = id(new PHUIPolicySectionView()) 327 + ->setViewer($viewer) 328 + ->setIcon($handle->getIcon().' bluegrey') 329 + ->setHeader(pht('Object Policy')) 330 + ->appendList( 331 + array( 332 + array( 333 + phutil_tag('strong', array(), pht('%s:', $capability_name)), 334 + ' ', 335 + $policy->getShortName(), 336 + ), 337 + )) 338 + ->appendParagraph( 339 + pht( 340 + 'In detail, this means that these users can take this action, '. 341 + 'provided they pass all of the checks described above first:')) 342 + ->appendList( 343 + array( 344 + PhabricatorPolicy::getPolicyExplanation( 345 + $viewer, 346 + $policy->getPHID()), 347 + )); 348 + 349 + $strength = $this->getStrengthInformation($object, $policy, $capability); 350 + if ($strength) { 351 + $object_section->appendHint($strength); 352 + } 353 + 354 + return $object_section; 210 355 } 211 356 212 357 }
-32
src/applications/policy/interface/PhabricatorPolicyInterface.php
··· 6 6 public function getPolicy($capability); 7 7 public function hasAutomaticCapability($capability, PhabricatorUser $viewer); 8 8 9 - /** 10 - * Describe exceptions to an object's policy setting. 11 - * 12 - * The intent of this method is to explain and inform users about special 13 - * cases which override configured policy settings. If this object has any 14 - * such exceptions, explain them by returning one or more human-readable 15 - * strings which describe the exception in a broad, categorical way. For 16 - * example: 17 - * 18 - * - "The owner of an X can always view and edit it." 19 - * - "Members of a Y can always view it." 20 - * 21 - * You can return `null`, a single string, or a list of strings. 22 - * 23 - * The relevant capability to explain (like "view") is passed as a parameter. 24 - * You should tailor any messages to be relevant to that capability, although 25 - * they do not need to exclusively describe the capability, and in some cases 26 - * being more general ("The author can view and edit...") will be more clear. 27 - * 28 - * Messages should describe general rules, not specific objects, because the 29 - * main goal is to teach the user the rules. For example, write "the author", 30 - * not the specific author's name. 31 - * 32 - * @param const @{class:PhabricatorPolicyCapability} constant. 33 - * @return wild Description of policy exceptions. See above. 34 - */ 35 - public function describeAutomaticCapability($capability); 36 - 37 9 } 38 10 39 11 // TEMPLATE IMPLEMENTATION ///////////////////////////////////////////////////// ··· 56 28 57 29 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 58 30 return false; 59 - } 60 - 61 - public function describeAutomaticCapability($capability) { 62 - return null; 63 31 } 64 32 65 33 */
+3 -1
src/applications/policy/storage/PhabricatorPolicy.php
··· 225 225 226 226 switch ($policy) { 227 227 case PhabricatorPolicies::POLICY_PUBLIC: 228 - return pht('This object is public.'); 228 + return pht( 229 + 'This object is public and can be viewed by anyone, even if they '. 230 + 'do not have a Phabricator account.'); 229 231 case PhabricatorPolicies::POLICY_USER: 230 232 return pht('Logged in users can take this action.'); 231 233 case PhabricatorPolicies::POLICY_ADMIN:
+142
src/applications/policy/view/PHUIPolicySectionView.php
··· 1 + <?php 2 + 3 + final class PHUIPolicySectionView 4 + extends AphrontTagView { 5 + 6 + private $icon; 7 + private $header; 8 + private $documentationLink; 9 + 10 + public function setHeader($header) { 11 + $this->header = $header; 12 + return $this; 13 + } 14 + 15 + public function getHeader() { 16 + return $this->header; 17 + } 18 + 19 + public function setIcon($icon) { 20 + $this->icon = $icon; 21 + return $this; 22 + } 23 + 24 + public function getIcon() { 25 + return $this->icon; 26 + } 27 + 28 + public function setDocumentationLink($name, $href) { 29 + $link = phutil_tag( 30 + 'a', 31 + array( 32 + 'href' => $href, 33 + 'target' => '_blank', 34 + ), 35 + $name); 36 + 37 + $this->documentationLink = phutil_tag( 38 + 'div', 39 + array( 40 + 'class' => 'phui-policy-section-view-link', 41 + ), 42 + array( 43 + id(new PHUIIconView())->setIcon('fa-book'), 44 + $link, 45 + )); 46 + 47 + return $this; 48 + } 49 + 50 + public function getDocumentationLink() { 51 + return $this->documentationLink; 52 + } 53 + 54 + public function appendList(array $items) { 55 + foreach ($items as $key => $item) { 56 + $items[$key] = phutil_tag( 57 + 'li', 58 + array( 59 + 'class' => 'remarkup-list-item', 60 + ), 61 + $item); 62 + } 63 + 64 + $list = phutil_tag( 65 + 'ul', 66 + array( 67 + 'class' => 'remarkup-list', 68 + ), 69 + $items); 70 + 71 + return $this->appendChild($list); 72 + } 73 + 74 + public function appendHint($content) { 75 + $hint = phutil_tag( 76 + 'p', 77 + array( 78 + 'class' => 'phui-policy-section-view-hint', 79 + ), 80 + array( 81 + id(new PHUIIconView()) 82 + ->setIcon('fa-sticky-note bluegrey'), 83 + ' ', 84 + pht('Note:'), 85 + ' ', 86 + $content, 87 + )); 88 + 89 + return $this->appendChild($hint); 90 + } 91 + 92 + public function appendParagraph($content) { 93 + return $this->appendChild(phutil_tag('p', array(), $content)); 94 + } 95 + 96 + protected function getTagAttributes() { 97 + return array( 98 + 'class' => 'phui-policy-section-view', 99 + ); 100 + } 101 + 102 + protected function getTagContent() { 103 + require_celerity_resource('phui-header-view-css'); 104 + 105 + $icon_view = null; 106 + $icon = $this->getIcon(); 107 + if ($icon !== null) { 108 + $icon_view = id(new PHUIIconView()) 109 + ->setIcon($icon); 110 + } 111 + 112 + $header_view = phutil_tag( 113 + 'span', 114 + array( 115 + 'class' => 'phui-policy-section-view-header-text', 116 + ), 117 + $this->getHeader()); 118 + 119 + $header = phutil_tag( 120 + 'div', 121 + array( 122 + 'class' => 'phui-policy-section-view-header', 123 + ), 124 + array( 125 + $icon_view, 126 + $header_view, 127 + $this->getDocumentationLink(), 128 + )); 129 + 130 + return array( 131 + $header, 132 + phutil_tag( 133 + 'div', 134 + array( 135 + 'class' => 'phui-policy-section-view-body', 136 + ), 137 + $this->renderChildren()), 138 + ); 139 + } 140 + 141 + 142 + }
+34 -2
src/view/phui/PHUIHeaderView.php
··· 506 506 } 507 507 } 508 508 509 + $policy_name = array($policy->getShortName()); 510 + $policy_icon = $policy->getIcon().' bluegrey'; 511 + 512 + if ($object instanceof PhabricatorPolicyCodexInterface) { 513 + $codex = PhabricatorPolicyCodex::newFromObject($object, $viewer); 514 + 515 + $codex_name = $codex->getPolicyShortName($policy, $view_capability); 516 + if ($codex_name !== null) { 517 + $policy_name = $codex_name; 518 + } 519 + 520 + $codex_icon = $codex->getPolicyIcon($policy, $view_capability); 521 + if ($codex_icon !== null) { 522 + $policy_icon = $codex_icon; 523 + } 524 + 525 + $codex_classes = $codex->getPolicyTagClasses($policy, $view_capability); 526 + foreach ($codex_classes as $codex_class) { 527 + $container_classes[] = $codex_class; 528 + } 529 + } 530 + 531 + if (!is_array($policy_name)) { 532 + $policy_name = (array)$policy_name; 533 + } 534 + 535 + $arrow = id(new PHUIIconView()) 536 + ->setIcon('fa-angle-right') 537 + ->addClass('policy-tier-separator'); 538 + 539 + $policy_name = phutil_implode_html($arrow, $policy_name); 540 + 509 541 $icon = id(new PHUIIconView()) 510 - ->setIcon($policy->getIcon().' bluegrey'); 542 + ->setIcon($policy_icon); 511 543 512 544 $link = javelin_tag( 513 545 'a', ··· 516 548 'href' => '/policy/explain/'.$phid.'/'.$view_capability.'/', 517 549 'sigil' => 'workflow', 518 550 ), 519 - $policy->getShortName()); 551 + $policy_name); 520 552 521 553 return phutil_tag( 522 554 'span',
+4
webroot/rsrc/css/aphront/dialog-view.css
··· 128 128 width: 50%; 129 129 } 130 130 131 + .aphront-access-dialog .aphront-dialog-body { 132 + padding: 0 12px; 133 + } 134 + 131 135 .aphront-policy-rejection { 132 136 font-weight: bold; 133 137 }
+62
webroot/rsrc/css/phui/phui-header-view.css
··· 240 240 color: {$sh-orangetext}; 241 241 } 242 242 243 + .policy-header-callout.policy-adjusted-special { 244 + background: {$sh-indigobackground}; 245 + } 246 + 247 + .policy-header-callout.policy-adjusted-special .policy-link, 248 + .policy-header-callout.policy-adjusted-special .phui-icon-view { 249 + color: {$sh-indigotext}; 250 + } 251 + 252 + .policy-header-callout .policy-space-container { 253 + font-weight: bold; 254 + color: {$sh-redtext}; 255 + } 256 + 257 + .policy-header-callout .policy-tier-separator { 258 + padding: 0 0 0 4px; 259 + color: {$lightgreytext}; 260 + } 261 + 262 + 243 263 .phui-header-action-links .phui-mobile-menu { 244 264 display: none; 245 265 } ··· 333 353 .phui-header-view .phui-tag-shade-indigo a { 334 354 color: {$sh-indigotext}; 335 355 } 356 + 357 + .phui-policy-section-view { 358 + margin-bottom: 24px; 359 + } 360 + 361 + .phui-policy-section-view-header { 362 + background: {$bluebackground}; 363 + border-bottom: 1px solid {$lightblueborder}; 364 + padding: 4px 8px; 365 + color: {$darkbluetext}; 366 + margin-bottom: 8px; 367 + } 368 + 369 + .phui-policy-section-view-header-text { 370 + font-weight: bold; 371 + } 372 + 373 + .phui-policy-section-view-header .phui-icon-view { 374 + margin-right: 8px; 375 + } 376 + 377 + .phui-policy-section-view-link { 378 + float: right; 379 + } 380 + 381 + .phui-policy-section-view-link .phui-icon-view { 382 + color: {$bluetext}; 383 + } 384 + 385 + .phui-policy-section-view-hint { 386 + color: {$greytext}; 387 + background: {$lightbluebackground}; 388 + padding: 8px; 389 + } 390 + 391 + .phui-policy-section-view-body { 392 + padding: 0 12px; 393 + } 394 + 395 + .phui-policy-section-view-inactive-rule { 396 + color: {$greytext}; 397 + }