From 9a0dd55442d780c78c5a8695817e1643b048738b Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Wed, 25 Apr 2018 17:00:09 -0700 Subject: [PATCH 01/14] Extend PhabricatorPolicyCodex interface to handle "interesting" policy defaults Summary: Fixes T13128. Ref PHI590. This is a rough-and-ready implementation of a new `PhabricatorPolicyCodex->compareToDefaultPolicy()` method that subclasses can override to handle special cases of policy defaults. Also implements a `PolicyCodex` for Phriction documents, because the default policy of a Phriction document is the policy of the root document. I might break this change into two parts, one of which maintains the current behavior and another which implements `PhrictionDocumentPolicyCodex`. Test Plan: Created some Phriction docs, fiddled with policies, observed expected colors in the header. Will test more comprehensively after review for basic reasonable-ness. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, swisspol Maniphest Tasks: T13128 Differential Revision: https://secure.phabricator.com/D19409 --- src/__phutil_library_map__.php | 5 ++ .../codex/PhrictionDocumentPolicyCodex.php | 82 +++++++++++++++++++ .../controller/PhrictionEditController.php | 13 ++- .../phriction/storage/PhrictionDocument.php | 28 +++---- .../policy/codex/PhabricatorPolicyCodex.php | 21 +++++ .../PhabricatorPolicyStrengthConstants.php | 9 ++ .../PhabricatorPolicyExplainController.php | 44 +++++++--- .../policy/storage/PhabricatorPolicy.php | 17 ++-- src/view/phui/PHUIHeaderView.php | 59 ++++++++----- 9 files changed, 218 insertions(+), 60 deletions(-) create mode 100644 src/applications/phriction/codex/PhrictionDocumentPolicyCodex.php create mode 100644 src/applications/policy/constants/PhabricatorPolicyStrengthConstants.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9de57b2530..c21315f45e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3867,6 +3867,7 @@ phutil_register_library_map(array( 'PhabricatorPolicyRequestExceptionHandler' => 'aphront/handler/PhabricatorPolicyRequestExceptionHandler.php', 'PhabricatorPolicyRule' => 'applications/policy/rule/PhabricatorPolicyRule.php', 'PhabricatorPolicySearchEngineExtension' => 'applications/policy/engineextension/PhabricatorPolicySearchEngineExtension.php', + 'PhabricatorPolicyStrengthConstants' => 'applications/policy/constants/PhabricatorPolicyStrengthConstants.php', 'PhabricatorPolicyTestCase' => 'applications/policy/__tests__/PhabricatorPolicyTestCase.php', 'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php', 'PhabricatorPolicyType' => 'applications/policy/constants/PhabricatorPolicyType.php', @@ -4998,6 +4999,7 @@ phutil_register_library_map(array( 'PhrictionDocumentMoveToTransaction' => 'applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php', 'PhrictionDocumentPHIDType' => 'applications/phriction/phid/PhrictionDocumentPHIDType.php', 'PhrictionDocumentPathHeraldField' => 'applications/phriction/herald/PhrictionDocumentPathHeraldField.php', + 'PhrictionDocumentPolicyCodex' => 'applications/phriction/codex/PhrictionDocumentPolicyCodex.php', 'PhrictionDocumentQuery' => 'applications/phriction/query/PhrictionDocumentQuery.php', 'PhrictionDocumentSearchConduitAPIMethod' => 'applications/phriction/conduit/PhrictionDocumentSearchConduitAPIMethod.php', 'PhrictionDocumentSearchEngine' => 'applications/phriction/query/PhrictionDocumentSearchEngine.php', @@ -9669,6 +9671,7 @@ phutil_register_library_map(array( 'PhabricatorPolicyRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorPolicyRule' => 'Phobject', 'PhabricatorPolicySearchEngineExtension' => 'PhabricatorSearchEngineExtension', + 'PhabricatorPolicyStrengthConstants' => 'PhabricatorPolicyConstants', 'PhabricatorPolicyTestCase' => 'PhabricatorTestCase', 'PhabricatorPolicyTestObject' => array( 'Phobject', @@ -11060,6 +11063,7 @@ phutil_register_library_map(array( 'PhabricatorProjectInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorConduitResultInterface', + 'PhabricatorPolicyCodexInterface', ), 'PhrictionDocumentAuthorHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentContentHeraldField' => 'PhrictionDocumentHeraldField', @@ -11076,6 +11080,7 @@ phutil_register_library_map(array( 'PhrictionDocumentMoveToTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentPHIDType' => 'PhabricatorPHIDType', 'PhrictionDocumentPathHeraldField' => 'PhrictionDocumentHeraldField', + 'PhrictionDocumentPolicyCodex' => 'PhabricatorPolicyCodex', 'PhrictionDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhrictionDocumentSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PhrictionDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine', diff --git a/src/applications/phriction/codex/PhrictionDocumentPolicyCodex.php b/src/applications/phriction/codex/PhrictionDocumentPolicyCodex.php new file mode 100644 index 0000000000..748bec705b --- /dev/null +++ b/src/applications/phriction/codex/PhrictionDocumentPolicyCodex.php @@ -0,0 +1,82 @@ +getObject(); + $strongest_policy = $this->getStrongestPolicy(); + + $rules = array(); + $rules[] = $this->newRule() + ->setDescription( + pht('To view a wiki document, you must also be able to view all '. + 'of its ancestors. The most-restrictive view policy of this '. + 'document\'s ancestors is "%s".', + $strongest_policy->getShortName())) + ->setCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW)); + + $rules[] = $this->newRule() + ->setDescription( + pht('To edit a wiki document, you must also be able to view all '. + 'of its ancestors.')) + ->setCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT)); + + return $rules; + } + + public function getDefaultPolicy() { + $ancestors = $this->getObject()->getAncestors(); + if ($ancestors) { + $root = head($ancestors); + } else { + $root = $this->getObject(); + } + + $root_policy_phid = $root->getPolicy($this->getCapability()); + + return id(new PhabricatorPolicyQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs(array($root_policy_phid)) + ->executeOne(); + } + + public function compareToDefaultPolicy(PhabricatorPolicy $policy) { + $root_policy = $this->getDefaultPolicy(); + $strongest_policy = $this->getStrongestPolicy(); + + // Note that we never return 'weaker', because Phriction documents can + // never have weaker permissions than their parents. If this object has + // been set to weaker permissions anyway, return 'adjusted'. + if ($root_policy == $strongest_policy) { + $strength = null; + } else if ($strongest_policy->isStrongerThan($root_policy)) { + $strength = PhabricatorPolicyStrengthConstants::STRONGER; + } else { + $strength = PhabricatorPolicyStrengthConstants::ADJUSTED; + } + return $strength; + } + + private function getStrongestPolicy() { + $ancestors = $this->getObject()->getAncestors(); + $ancestors[] = $this->getObject(); + + $strongest_policy = $this->getDefaultPolicy(); + foreach ($ancestors as $ancestor) { + $ancestor_policy_phid = $ancestor->getPolicy($this->getCapability()); + + $ancestor_policy = id(new PhabricatorPolicyQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs(array($ancestor_policy_phid)) + ->executeOne(); + + if ($ancestor_policy->isStrongerThan($strongest_policy)) { + $strongest_policy = $ancestor_policy; + } + } + + return $strongest_policy; + } + +} diff --git a/src/applications/phriction/controller/PhrictionEditController.php b/src/applications/phriction/controller/PhrictionEditController.php index e7e2b40d87..006488b274 100644 --- a/src/applications/phriction/controller/PhrictionEditController.php +++ b/src/applications/phriction/controller/PhrictionEditController.php @@ -219,6 +219,13 @@ final class PhrictionEditController ->execute(); $view_capability = PhabricatorPolicyCapability::CAN_VIEW; $edit_capability = PhabricatorPolicyCapability::CAN_EDIT; + $codex = id(PhabricatorPolicyCodex::newFromObject($document, $viewer)) + ->setCapability($view_capability); + + $view_capability_description = $codex->getPolicySpecialRuleForCapability( + PhabricatorPolicyCapability::CAN_VIEW)->getDescription(); + $edit_capability_description = $codex->getPolicySpecialRuleForCapability( + PhabricatorPolicyCapability::CAN_EDIT)->getDescription(); $form = id(new AphrontFormView()) ->setUser($viewer) @@ -264,16 +271,14 @@ final class PhrictionEditController ->setPolicyObject($document) ->setCapability($view_capability) ->setPolicies($policies) - ->setCaption( - $document->describeAutomaticCapability($view_capability))) + ->setCaption($view_capability_description)) ->appendChild( id(new AphrontFormPolicyControl()) ->setName('editPolicy') ->setPolicyObject($document) ->setCapability($edit_capability) ->setPolicies($policies) - ->setCaption( - $document->describeAutomaticCapability($edit_capability))) + ->setCaption($edit_capability_description)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Edit Notes')) diff --git a/src/applications/phriction/storage/PhrictionDocument.php b/src/applications/phriction/storage/PhrictionDocument.php index 729d3e4ca5..f482579cea 100644 --- a/src/applications/phriction/storage/PhrictionDocument.php +++ b/src/applications/phriction/storage/PhrictionDocument.php @@ -11,7 +11,8 @@ final class PhrictionDocument extends PhrictionDAO PhabricatorFerretInterface, PhabricatorProjectInterface, PhabricatorApplicationTransactionInterface, - PhabricatorConduitResultInterface { + PhabricatorConduitResultInterface, + PhabricatorPolicyCodexInterface { protected $slug; protected $depth; @@ -200,22 +201,6 @@ final class PhrictionDocument extends PhrictionDAO return false; } - public function describeAutomaticCapability($capability) { - - switch ($capability) { - case PhabricatorPolicyCapability::CAN_VIEW: - return pht( - 'To view a wiki document, you must also be able to view all '. - 'of its parents.'); - case PhabricatorPolicyCapability::CAN_EDIT: - return pht( - 'To edit a wiki document, you must also be able to view all '. - 'of its parents.'); - } - - return null; - } - /* -( PhabricatorSubscribableInterface )----------------------------------- */ @@ -328,4 +313,13 @@ final class PhrictionDocument extends PhrictionDAO ->setAttachmentKey('content'), ); } + +/* -( PhabricatorPolicyCodexInterface )------------------------------------ */ + + + public function newPolicyCodex() { + return new PhrictionDocumentPolicyCodex(); + } + + } diff --git a/src/applications/policy/codex/PhabricatorPolicyCodex.php b/src/applications/policy/codex/PhabricatorPolicyCodex.php index 43a1cc5a1d..060a798e0a 100644 --- a/src/applications/policy/codex/PhabricatorPolicyCodex.php +++ b/src/applications/policy/codex/PhabricatorPolicyCodex.php @@ -29,6 +29,27 @@ abstract class PhabricatorPolicyCodex return array(); } + public function getDefaultPolicy() { + return PhabricatorPolicyQuery::getDefaultPolicyForObject( + $this->viewer, + $this->object, + $this->capability); + } + + public function compareToDefaultPolicy(PhabricatorPolicy $policy) { + return null; + } + + final public function getPolicySpecialRuleForCapability($capability) { + foreach ($this->getPolicySpecialRuleDescriptions() as $rule) { + if (in_array($capability, $rule->getCapabilities())) { + return $rule; + } + } + + return null; + } + final protected function newRule() { return new PhabricatorPolicyCodexRuleDescription(); } diff --git a/src/applications/policy/constants/PhabricatorPolicyStrengthConstants.php b/src/applications/policy/constants/PhabricatorPolicyStrengthConstants.php new file mode 100644 index 0000000000..9bc3c81ca2 --- /dev/null +++ b/src/applications/policy/constants/PhabricatorPolicyStrengthConstants.php @@ -0,0 +1,9 @@ +getViewer(); - $default_policy = PhabricatorPolicyQuery::getDefaultPolicyForObject( - $viewer, - $object, - $capability); - if (!$default_policy) { + + $strength = null; + if ($object instanceof PhabricatorPolicyCodexInterface) { + $codex = id(PhabricatorPolicyCodex::newFromObject($object, $viewer)) + ->setCapability($capability); + $strength = $codex->compareToDefaultPolicy($policy); + $default_policy = $codex->getDefaultPolicy(); + } else { + $default_policy = PhabricatorPolicyQuery::getDefaultPolicyForObject( + $viewer, + $object, + $capability); + + if ($default_policy) { + if ($default_policy->getPHID() == $policy->getPHID()) { + return; + } + + if ($default_policy->getPHID() != $policy->getPHID()) { + if ($default_policy->isStrongerThan($policy)) { + $strength = PhabricatorPolicyStrengthConstants::WEAKER; + } else if ($policy->isStrongerThan($default_policy)) { + $strength = PhabricatorPolicyStrengthConstants::STRONGER; + } else { + $strength = PhabricatorPolicyStrengthConstants::ADJUSTED; + } + } + } + } + + if (!$strength) { return; } - if ($default_policy->getPHID() == $policy->getPHID()) { - return; - } - - if ($default_policy->isStrongerThan($policy)) { + if ($strength == PhabricatorPolicyStrengthConstants::WEAKER) { $info = pht( 'This object has a less restrictive policy ("%s") than the default '. 'policy for similar objects (which is "%s").', $policy->getShortName(), $default_policy->getShortName()); - } else if ($policy->isStrongerThan($default_policy)) { + } else if ($strength == PhabricatorPolicyStrengthConstants::STRONGER) { $info = pht( 'This object has a more restrictive policy ("%s") than the default '. 'policy for similar objects (which is "%s").', diff --git a/src/applications/policy/storage/PhabricatorPolicy.php b/src/applications/policy/storage/PhabricatorPolicy.php index 4141ef9c36..2df8fdf6a0 100644 --- a/src/applications/policy/storage/PhabricatorPolicy.php +++ b/src/applications/policy/storage/PhabricatorPolicy.php @@ -434,11 +434,12 @@ final class PhabricatorPolicy $capability, $active_only) { + $exceptions = array(); if ($object instanceof PhabricatorPolicyCodexInterface) { - $codex = PhabricatorPolicyCodex::newFromObject($object, $viewer); + $codex = id(PhabricatorPolicyCodex::newFromObject($object, $viewer)) + ->setCapability($capability); $rules = $codex->getPolicySpecialRuleDescriptions(); - $exceptions = array(); foreach ($rules as $rule) { $is_active = $rule->getIsActive(); if ($is_active) { @@ -467,11 +468,13 @@ final class PhabricatorPolicy $exceptions[] = $description; } - } else if (method_exists($object, 'describeAutomaticCapability')) { - $exceptions = (array)$object->describeAutomaticCapability($capability); - $exceptions = array_filter($exceptions); - } else { - $exceptions = array(); + } + + if (!$exceptions) { + if (method_exists($object, 'describeAutomaticCapability')) { + $exceptions = (array)$object->describeAutomaticCapability($capability); + $exceptions = array_filter($exceptions); + } } return $exceptions; diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php index 4ac4b2de54..54f4fa58ee 100644 --- a/src/view/phui/PHUIHeaderView.php +++ b/src/view/phui/PHUIHeaderView.php @@ -473,30 +473,47 @@ final class PHUIHeaderView extends AphrontTagView { // policy differs from the default policy. If it does, we'll call it out // as changed. if (!$use_space_policy) { - $default_policy = PhabricatorPolicyQuery::getDefaultPolicyForObject( - $viewer, - $object, - $view_capability); - if ($default_policy) { - if ($default_policy->getPHID() != $policy->getPHID()) { - $container_classes[] = 'policy-adjusted'; - if ($default_policy->isStrongerThan($policy)) { - // The policy has strictly been weakened. For example, the - // default might be "All Users" and the current policy is "Public". - $container_classes[] = 'policy-adjusted-weaker'; - } else if ($policy->isStrongerThan($default_policy)) { - // The policy has strictly been strengthened, and is now more - // restrictive than the default. For example, "All Users" has - // been replaced with "No One". - $container_classes[] = 'policy-adjusted-stronger'; - } else { - // The policy has been adjusted but not strictly strengthened - // or weakened. For example, "Members of X" has been replaced with - // "Members of Y". - $container_classes[] = 'policy-adjusted-different'; + $strength = null; + if ($object instanceof PhabricatorPolicyCodexInterface) { + $codex = id(PhabricatorPolicyCodex::newFromObject($object, $viewer)) + ->setCapability($view_capability); + $strength = $codex->compareToDefaultPolicy($policy); + } else { + $default_policy = PhabricatorPolicyQuery::getDefaultPolicyForObject( + $viewer, + $object, + $view_capability); + + if ($default_policy) { + if ($default_policy->getPHID() != $policy->getPHID()) { + if ($default_policy->isStrongerThan($policy)) { + $strength = PhabricatorPolicyStrengthConstants::WEAKER; + } else if ($policy->isStrongerThan($default_policy)) { + $strength = PhabricatorPolicyStrengthConstants::STRONGER; + } else { + $strength = PhabricatorPolicyStrengthConstants::ADJUSTED; + } } } } + + if ($strength) { + if ($strength == PhabricatorPolicyStrengthConstants::WEAKER) { + // The policy has strictly been weakened. For example, the + // default might be "All Users" and the current policy is "Public". + $container_classes[] = 'policy-adjusted-weaker'; + } else if ($strength == PhabricatorPolicyStrengthConstants::STRONGER) { + // The policy has strictly been strengthened, and is now more + // restrictive than the default. For example, "All Users" has + // been replaced with "No One". + $container_classes[] = 'policy-adjusted-stronger'; + } else { + // The policy has been adjusted but not strictly strengthened + // or weakened. For example, "Members of X" has been replaced with + // "Members of Y". + $container_classes[] = 'policy-adjusted-different'; + } + } } $policy_name = array($policy->getShortName()); From ef48a2b2eebc8bb513c3b7dca1f1e4847900c058 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 28 Apr 2018 06:12:14 -0700 Subject: [PATCH 02/14] Add a "Rule Detail" link to Herald email Summary: See PHI285. Ref T13130. After recent changes Herald sends email about rules, but the mail doesn't currently actually include a link to the rule. Include a link for consistency and ease-of-use. Test Plan: Edited a rule, looked at the resulting mail, saw a link to the rule. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13130 Differential Revision: https://secure.phabricator.com/D19413 --- .../herald/editor/HeraldRuleEditor.php | 14 ++++++++++++++ src/applications/herald/storage/HeraldRule.php | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/src/applications/herald/editor/HeraldRuleEditor.php b/src/applications/herald/editor/HeraldRuleEditor.php index 8c83ef6597..3ba5c4f8ac 100644 --- a/src/applications/herald/editor/HeraldRuleEditor.php +++ b/src/applications/herald/editor/HeraldRuleEditor.php @@ -137,4 +137,18 @@ final class HeraldRuleEditor return pht('[Herald]'); } + + protected function buildMailBody( + PhabricatorLiskDAO $object, + array $xactions) { + + $body = parent::buildMailBody($object, $xactions); + + $body->addLinkSection( + pht('RULE DETAIL'), + PhabricatorEnv::getProductionURI($object->getURI())); + + return $body; + } + } diff --git a/src/applications/herald/storage/HeraldRule.php b/src/applications/herald/storage/HeraldRule.php index 44ef68ac03..d2b8d36f05 100644 --- a/src/applications/herald/storage/HeraldRule.php +++ b/src/applications/herald/storage/HeraldRule.php @@ -254,6 +254,10 @@ final class HeraldRule extends HeraldDAO return 'H'.$this->getID(); } + public function getURI() { + return '/'.$this->getMonogram(); + } + /* -( Repetition Policies )------------------------------------------------ */ From dd6e82698aebc93bd09b033e8a7a030f320ff9bf Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Mon, 30 Apr 2018 12:10:37 -0700 Subject: [PATCH 03/14] More-robust search for task assignees Summary: See discussion in D19415. Test Plan: Searched for some owners, found tasks as expected. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D19417 --- src/applications/maniphest/query/ManiphestTaskQuery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php index edb780b724..16aa83a2ea 100644 --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -585,7 +585,7 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { 'task.ownerPHID IS NOT NULL'); } - if ($this->ownerPHIDs) { + if ($this->ownerPHIDs !== null) { $subclause[] = qsprintf( $conn, 'task.ownerPHID IN (%Ls)', From afc3099ee785c0f9cb86c702fc1d04d2be9f6af5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 28 Apr 2018 06:43:09 -0700 Subject: [PATCH 04/14] Add a view option to disable blame in Diffusion and fix some view transition bugs Summary: See PHI604. Ref T13130. Ref T13105. There's currently no way to turn blame off in Diffusion. Add a "Hide Blame" option to the "View Options" dropdown so it can be toggled off. Also fix a couple of bugs around this: for example, if you loaded a Jupyter notebook and then switched to "Source" view, blame would incorrectly fail to activate because the original rendering of the "stage" used an asynchronous engine so `willRenderRef()` wasn't called to populate blame. Test Plan: - Viewed a source file, toggled blame off/on, reloaded page to see state stick in URL. - Viewed a Jupyter notebook, toggled to "Source" view, saw blame. - Viewed stuff in Files (no blame UI options). - Tried to do some invalid stuff like toggle blame on a non-blame engine (options disable properly). Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13130, T13105 Differential Revision: https://secure.phabricator.com/D19414 --- resources/celerity/map.php | 14 +++--- .../DiffusionDocumentRenderingEngine.php | 12 +++-- .../document/PhabricatorDocumentEngine.php | 14 ++++++ .../PhabricatorSourceDocumentEngine.php | 2 +- .../PhabricatorDocumentRenderingEngine.php | 17 +++++++ .../files/behavior-document-engine.js | 45 ++++++++++++++++++- 6 files changed, 91 insertions(+), 13 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 480c4b602d..78b3660c4c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -388,7 +388,7 @@ return array( 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc', 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '1db13e70', 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef', - 'rsrc/js/application/files/behavior-document-engine.js' => 'ee0deff8', + 'rsrc/js/application/files/behavior-document-engine.js' => '3935d8c4', 'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab', 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '549459b8', @@ -600,7 +600,7 @@ return array( 'javelin-behavior-diffusion-commit-graph' => '75b83cbb', 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', - 'javelin-behavior-document-engine' => 'ee0deff8', + 'javelin-behavior-document-engine' => '3935d8c4', 'javelin-behavior-doorkeeper-tag' => '1db13e70', 'javelin-behavior-drydock-live-operation-status' => '901935ef', 'javelin-behavior-durable-column' => '2ae077e1', @@ -1080,6 +1080,11 @@ return array( 'javelin-dom', 'javelin-vector', ), + '3935d8c4' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -2105,11 +2110,6 @@ return array( 'javelin-behavior', 'javelin-uri', ), - 'ee0deff8' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - ), 'efe49472' => array( 'javelin-install', 'javelin-util', diff --git a/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php b/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php index 9615b35799..800d195726 100644 --- a/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php +++ b/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php @@ -69,7 +69,7 @@ final class DiffusionDocumentRenderingEngine return; } - protected function willRenderRef(PhabricatorDocumentRef $ref) { + protected function willStageRef(PhabricatorDocumentRef $ref) { $drequest = $this->getDiffusionRequest(); $blame_uri = (string)$drequest->generateURI( @@ -78,9 +78,13 @@ final class DiffusionDocumentRenderingEngine 'stable' => true, )); - $ref - ->setSymbolMetadata($this->getSymbolMetadata()) - ->setBlameURI($blame_uri); + $ref->setBlameURI($blame_uri); + } + + protected function willRenderRef(PhabricatorDocumentRef $ref) { + $drequest = $this->getDiffusionRequest(); + + $ref->setSymbolMetadata($this->getSymbolMetadata()); $coverage = $drequest->loadCoverage(); if (strlen($coverage)) { diff --git a/src/applications/files/document/PhabricatorDocumentEngine.php b/src/applications/files/document/PhabricatorDocumentEngine.php index 85219b904c..c869f5f0d7 100644 --- a/src/applications/files/document/PhabricatorDocumentEngine.php +++ b/src/applications/files/document/PhabricatorDocumentEngine.php @@ -7,6 +7,7 @@ abstract class PhabricatorDocumentEngine private $highlightedLines = array(); private $encodingConfiguration; private $highlightingConfiguration; + private $blameConfiguration = true; final public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -60,6 +61,19 @@ abstract class PhabricatorDocumentEngine return $this->highlightingConfiguration; } + final public function setBlameConfiguration($blame_configuration) { + $this->blameConfiguration = $blame_configuration; + return $this; + } + + final public function getBlameConfiguration() { + return $this->blameConfiguration; + } + + final protected function getBlameEnabled() { + return $this->blameConfiguration; + } + public function shouldRenderAsync(PhabricatorDocumentRef $ref) { return false; } diff --git a/src/applications/files/document/PhabricatorSourceDocumentEngine.php b/src/applications/files/document/PhabricatorSourceDocumentEngine.php index c88d979b4e..5d7c0cdb75 100644 --- a/src/applications/files/document/PhabricatorSourceDocumentEngine.php +++ b/src/applications/files/document/PhabricatorSourceDocumentEngine.php @@ -50,7 +50,7 @@ final class PhabricatorSourceDocumentEngine } $options = array(); - if ($ref->getBlameURI()) { + if ($ref->getBlameURI() && $this->getBlameEnabled()) { $content = phutil_split_lines($content); $blame = range(1, count($content)); $blame = array_fuse($blame); diff --git a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php index 155ed28dfa..2524f77821 100644 --- a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php +++ b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php @@ -69,6 +69,9 @@ abstract class PhabricatorDocumentRenderingEngine $engine->setHighlightingConfiguration($highlight_setting); } + $blame_setting = ($request->getStr('blame') !== 'off'); + $engine->setBlameConfiguration($blame_setting); + $views = array(); foreach ($engines as $candidate_key => $candidate_engine) { $label = $candidate_engine->getViewAsLabel($ref); @@ -104,6 +107,8 @@ abstract class PhabricatorDocumentRenderingEngine 'controlID' => $control_id, ); + $this->willStageRef($ref); + if ($engine->shouldRenderAsync($ref)) { $content = $engine->newLoadingContent($ref); $config['next'] = 'render'; @@ -142,7 +147,11 @@ abstract class PhabricatorDocumentRenderingEngine 'value' => $highlight_setting, ), 'blame' => array( + 'icon' => 'fa-backward', + 'hide' => pht('Hide Blame'), + 'show' => pht('Show Blame'), 'uri' => $ref->getBlameURI(), + 'enabled' => $blame_setting, 'value' => null, ), 'coverage' => array( @@ -180,6 +189,7 @@ abstract class PhabricatorDocumentRenderingEngine } final public function newRenderResponse(PhabricatorDocumentRef $ref) { + $this->willStageRef($ref); $this->willRenderRef($ref); $request = $this->getRequest(); @@ -207,6 +217,9 @@ abstract class PhabricatorDocumentRenderingEngine $engine->setHighlightingConfiguration($highlight_setting); } + $blame_setting = ($request->getStr('blame') !== 'off'); + $engine->setBlameConfiguration($blame_setting); + try { $content = $engine->newDocument($ref); } catch (Exception $ex) { @@ -314,6 +327,10 @@ abstract class PhabricatorDocumentRenderingEngine return; } + protected function willStageRef(PhabricatorDocumentRef $ref) { + return; + } + protected function willRenderRef(PhabricatorDocumentRef $ref) { return; } diff --git a/webroot/rsrc/js/application/files/behavior-document-engine.js b/webroot/rsrc/js/application/files/behavior-document-engine.js index 8b957e0ea0..caad5977bc 100644 --- a/webroot/rsrc/js/application/files/behavior-document-engine.js +++ b/webroot/rsrc/js/application/files/behavior-document-engine.js @@ -105,6 +105,29 @@ JX.behavior('document-engine', function(config, statics) { list.addItem(highlight_item); + var blame_item; + if (data.blame.uri) { + blame_item = new JX.PHUIXActionView() + .setIcon(data.blame.icon); + + var onblame = JX.bind(null, function(data, e) { + e.prevent(); + + if (blame_item.getDisabled()) { + return; + } + + data.blame.enabled = !data.blame.enabled; + onview(data); + + menu.close(); + }, data); + + blame_item.setHandler(onblame); + + list.addItem(blame_item); + } + menu.setContent(list.getNode()); menu.listen('open', function() { @@ -118,8 +141,22 @@ JX.behavior('document-engine', function(config, statics) { if (is_selected) { encode_item.setDisabled(!engine.spec.canEncode); highlight_item.setDisabled(!engine.spec.canHighlight); + if (blame_item) { + blame_item.setDisabled(!engine.spec.canBlame); + } } } + + if (blame_item) { + var blame_label; + if (data.blame.enabled) { + blame_label = data.blame.hide; + } else { + blame_label = data.blame.show; + } + + blame_item.setName(blame_label); + } }); data.menu = menu; @@ -137,6 +174,12 @@ JX.behavior('document-engine', function(config, statics) { uri.setQueryParam('encode', data.encode.value); } + if (data.blame.enabled) { + uri.setQueryParam('blame', null); + } else { + uri.setQueryParam('blame', 'off'); + } + return uri.toString(); } @@ -211,7 +254,7 @@ JX.behavior('document-engine', function(config, statics) { JX.DOM.setContent(viewport, JX.$H(r.markup)); // If this engine supports rendering blame, populate or draw it. - if (spec.canBlame) { + if (spec.canBlame && data.blame.enabled) { blame(data); } } From 24305cadb9026a744a554e26b75e5a1e89c3ae32 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 30 Apr 2018 11:51:55 -0700 Subject: [PATCH 05/14] Hide the "large" diff warning on "very large" diffs Summary: Ref T13110. Ref T13130. When a revision is "large" (100 - 1000 files) we hide the actual textual changes by default. When it is "very large" (more than 1000 files) we hide all the changesets by default. For "very large" diffs, we currently still show the "large" warning, which doesn't really make sense since there aren't any actual changesets. When a diff is "very large", don't show the "large" warning. Test Plan: - Viewed a small diff (<100 files), saw no warnings. - Viewed a large diff (100-1000 files), saw just the large warning. - Viewed a very large diff (>1000 files). - Before: both "large" and "very large" help warnings. - After: just "very large" warnings. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13130, T13110 Differential Revision: https://secure.phabricator.com/D19416 --- .../controller/DifferentialRevisionViewController.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 9b015887a6..9672654c58 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -153,9 +153,17 @@ final class DifferentialRevisionViewController extends DifferentialController { $request_uri = $request->getRequestURI(); + // Revisions with more than 100 files are "large". + // Revisions with more than 1000 files are "very large". $limit = 100; $large = $request->getStr('large'); - if (count($changesets) > $limit && !$large) { + + $large_warning = + (!$this->isVeryLargeDiff()) && + (count($changesets) > $limit) && + (!$large); + + if ($large_warning) { $count = count($changesets); $warning = new PHUIInfoView(); $warning->setTitle(pht('Large Diff')); From ee32c186dd822b90826514ff8fc61740ebc8bd68 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 30 Apr 2018 12:06:16 -0700 Subject: [PATCH 06/14] Stop computing ownership for changed paths for Very Large revisions Summary: Depends on D19416. Ref T13110. Ref T13130. See PHI598. When rendering a "Very Large" revision (affecting more than 1,000 files) we currently compute the package/changeset ownership map normally. This is basically a big list of which packages own which of the files affected by the change. We use it to: # Show which packages own each file in the table of contents. # Show an "(Owns No Changed Paths)" hint in the reviewers list to help catch out-of-date packages that are no longer relevant. However, this is expensive to build. We don't render the table of contents at all, so (1) is pointless. The value of (2) is very small on these types of changes, and certainly not worth spending many many seconds computing ownership. Instead, just skip building out these relationships for very large changes. Test Plan: Viewed a very large change with package owners; verified it no longer built package map data and rendered the package owners with no "(Owns No Changed Paths)" hints. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13130, T13110 Differential Revision: https://secure.phabricator.com/D19418 --- .../DifferentialRevisionViewController.php | 67 +++++++++++++------ 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 9672654c58..9959bfabab 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -1,22 +1,43 @@ getChangesetCount() > $this->getLargeDiffLimit()); + } + public function isVeryLargeDiff() { - return $this->veryLargeDiff; + return ($this->getChangesetCount() > $this->getVeryLargeDiffLimit()); + } + + public function getLargeDiffLimit() { + return 100; } public function getVeryLargeDiffLimit() { return 1000; } + public function getChangesetCount() { + if ($this->changesetCount === null) { + throw new PhutilInvalidStateException('setChangesetCount'); + } + return $this->changesetCount; + } + + public function setChangesetCount($count) { + $this->changesetCount = $count; + return $this; + } + public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); $this->revisionID = $request->getURIData('id'); @@ -82,9 +103,7 @@ final class DifferentialRevisionViewController extends DifferentialController { idx($diffs, $diff_vs), $repository); - if (count($rendering_references) > $this->getVeryLargeDiffLimit()) { - $this->veryLargeDiff = true; - } + $this->setChangesetCount(count($rendering_references)); if ($request->getExists('download')) { return $this->buildRawDiffResponse( @@ -153,18 +172,16 @@ final class DifferentialRevisionViewController extends DifferentialController { $request_uri = $request->getRequestURI(); - // Revisions with more than 100 files are "large". - // Revisions with more than 1000 files are "very large". - $limit = 100; $large = $request->getStr('large'); $large_warning = + ($this->isLargeDiff()) && (!$this->isVeryLargeDiff()) && - (count($changesets) > $limit) && (!$large); if ($large_warning) { - $count = count($changesets); + $count = $this->getChangesetCount(); + $warning = new PHUIInfoView(); $warning->setTitle(pht('Large Diff')); $warning->setSeverity(PHUIInfoView::SEVERITY_WARNING); @@ -365,23 +382,33 @@ final class DifferentialRevisionViewController extends DifferentialController { $other_view = $this->renderOtherRevisions($other_revisions); } - $this->buildPackageMaps($changesets); - if ($this->isVeryLargeDiff()) { $toc_view = null; + + // When rendering a "very large" diff, we skip computation of owners + // that own no files because it is significantly expensive and not very + // valuable. + foreach ($revision->getReviewers() as $reviewer) { + // Give each reviewer a dummy nonempty value so the UI does not render + // the "(Owns No Changed Paths)" note. If that behavior becomes more + // sophisticated in the future, this behavior might also need to. + $reviewer->attachChangesets($changesets); + } } else { + $this->buildPackageMaps($changesets); + $toc_view = $this->buildTableOfContents( $changesets, $visible_changesets, $target->loadCoverageMap($viewer)); - } - // Attach changesets to each reviewer so we can show which Owners package - // reviewers own no files. - foreach ($revision->getReviewers() as $reviewer) { - $reviewer_phid = $reviewer->getReviewerPHID(); - $reviewer_changesets = $this->getPackageChangesets($reviewer_phid); - $reviewer->attachChangesets($reviewer_changesets); + // Attach changesets to each reviewer so we can show which Owners package + // reviewers own no files. + foreach ($revision->getReviewers() as $reviewer) { + $reviewer_phid = $reviewer->getReviewerPHID(); + $reviewer_changesets = $this->getPackageChangesets($reviewer_phid); + $reviewer->attachChangesets($reviewer_changesets); + } } $tab_group = id(new PHUITabGroupView()); From 7cfac40a225bdc95080837245444afa4e122085d Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 24 Apr 2018 16:13:29 -0700 Subject: [PATCH 07/14] Pass full Harbormaster URIs to Buildkite Summary: See PHI611 for details. Test Plan: Ran a Buildkite build, saw Buildkite confirm receipt of these parameters in the HTTP response: {F5562054} Reviewers: amckinley Reviewed By: amckinley Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Differential Revision: https://secure.phabricator.com/D19419 --- .../step/HarbormasterBuildkiteBuildStepImplementation.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php index 89d4002eef..8a522c4926 100644 --- a/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php @@ -106,6 +106,14 @@ EOTEXT ), 'meta_data' => array( 'buildTargetPHID' => $build_target->getPHID(), + + // See PHI611. These are undocumented secret magic. + 'phabricator:build:id' => (int)$build->getID(), + 'phabricator:build:url' => + PhabricatorEnv::getProductionURI($build->getURI()), + 'phabricator:buildable:id' => (int)$buildable->getID(), + 'phabricator:buildable:url' => + PhabricatorEnv::getProductionURI($buildable->getURI()), ), ); From fb4b9bc2fc61264f7d6f6e61e5675a1d261d679d Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 1 May 2018 06:55:25 -0700 Subject: [PATCH 08/14] Fix an issue where entering the same Owners path for two repositories would incorrectly de-dupe the path Summary: Ref T13130. See . When you edit paths in Owners, we deduplicate similar paths, like `/x/y` and `/x/y/`. However, this logic currently only examines the paths, and incorrectly deduplicates the same path in different repositories. Instead, consider the repository before deduplicating. Test Plan: - Edited an Owners package and added the path "/" in two different repositories. - Before: only one surived the edit. - After: both survived. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13130 Differential Revision: https://secure.phabricator.com/D19420 --- .../PhabricatorOwnersPackagePathsTransaction.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/applications/owners/xaction/PhabricatorOwnersPackagePathsTransaction.php b/src/applications/owners/xaction/PhabricatorOwnersPackagePathsTransaction.php index 984b26b8ac..753b6ff9e9 100644 --- a/src/applications/owners/xaction/PhabricatorOwnersPackagePathsTransaction.php +++ b/src/applications/owners/xaction/PhabricatorOwnersPackagePathsTransaction.php @@ -108,19 +108,23 @@ final class PhabricatorOwnersPackagePathsTransaction // paths now. $display_map = array(); + $seen_map = array(); foreach ($new as $key => $spec) { $display_path = $spec['path']; $raw_path = rtrim($display_path, '/').'/'; - // If the user entered two paths which normalize to the same value - // (like "src/main.c" and "src/main.c/"), discard the duplicates. - if (isset($display_map[$raw_path])) { + // If the user entered two paths in the same repository which normalize + // to the same value (like "src/main.c" and "src/main.c/"), discard the + // duplicates. + $repository_phid = $spec['repositoryPHID']; + if (isset($seen_map[$repository_phid][$raw_path])) { unset($new[$key]); continue; } $new[$key]['path'] = $raw_path; $display_map[$raw_path] = $display_path; + $seen_map[$repository_phid][$raw_path] = true; } $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); From 332f4ab66d185e3b41146f48c7bec3914445be39 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 1 May 2018 07:08:45 -0700 Subject: [PATCH 09/14] Restore support for using "arc download" to fetch files with no "security.alternate-file-domain" Summary: Fixes T13132. I removed this branch in D19156 when tightening the logic for the new CSP header, but there's a legitimate need for it: downloading files via `arc download`, or more generally being an API consumer of files. This is not completely safe, but attacks I'm aware of (particularly, cookie fixation, where an attacker could potentially force a victim to become logged in to an account they control) are difficult and not very powerful. We already issue clear setup advice about the importance of configuring this option ("Phabricator is currently configured to serve user uploads directly from the same domain as other content. This is a security risk.") and I think there's significant value in letting API clients just GET file data without having to jump through a lot of weird hoops. Test Plan: - With `security.alternate-file-domain` off, tried to `arc download` a file. - Before: downloaded an HTML dialog page. - After: downloaded the file. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13132 Differential Revision: https://secure.phabricator.com/D19421 --- .../PhabricatorFileDataController.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/applications/files/controller/PhabricatorFileDataController.php b/src/applications/files/controller/PhabricatorFileDataController.php index bd3d933283..dd5e0adb7d 100644 --- a/src/applications/files/controller/PhabricatorFileDataController.php +++ b/src/applications/files/controller/PhabricatorFileDataController.php @@ -84,12 +84,29 @@ final class PhabricatorFileDataController extends PhabricatorFileController { $response->setMimeType($file->getViewableMimeType()); } else { $is_post = $request->isHTTPPost(); + $is_public = !$viewer->isLoggedIn(); // NOTE: Require POST to download files from the primary domain. If the // request is not a POST request but arrives on the primary domain, we // render a confirmation dialog. For discussion, see T13094. - $is_safe = ($is_alternate_domain || $is_lfs || $is_post); + // There are two exceptions to this rule: + + // Git LFS requests can download with GET. This is safe (Git LFS won't + // execute files it downloads) and necessary to support Git LFS. + + // Requests with no credentials may also download with GET. This + // primarily supports downloading files with `arc download` or other + // API clients. This is only "mostly" safe: if you aren't logged in, you + // are likely immune to XSS and CSRF. However, an attacker may still be + // able to set cookies on this domain (for example, to fixate your + // session). For now, we accept these risks because users running + // Phabricator in this mode are knowingly accepting a security risk + // against setup advice, and there's significant value in having + // API development against test and production installs work the same + // way. + + $is_safe = ($is_alternate_domain || $is_post || $is_lfs || $is_public); if (!$is_safe) { return $this->newDialog() ->setSubmitURI($file->getDownloadURI()) From 5784e3d3c0cf817512023b3d797c03290ae26a8c Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 4 May 2018 09:20:08 -0700 Subject: [PATCH 10/14] Omit "type" attribute from "" tags in "