diff --git a/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php b/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php index 11e864e74e..32a73867e5 100644 --- a/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php +++ b/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php @@ -1,132 +1,132 @@ array('cmds', '*'), 'between' => array('pairs'), 'branchmap' => array(), 'branches' => array('nodes'), 'capabilities' => array(), 'changegroup' => array('roots'), 'changegroupsubset' => array('bases heads'), 'debugwireargs' => array('one two *'), 'getbundle' => array('*'), 'heads' => array(), 'hello' => array(), 'known' => array('nodes', '*'), 'listkeys' => array('namespace'), 'lookup' => array('key'), 'pushkey' => array('namespace', 'key', 'old', 'new'), 'stream_out' => array(''), 'unbundle' => array('heads'), ); if (!isset($commands[$command])) { throw new Exception(pht("Unknown Mercurial command '%s!", $command)); } return $commands[$command]; } public static function isReadOnlyCommand($command) { $read_only = array( 'between' => true, 'branchmap' => true, 'branches' => true, 'capabilities' => true, 'changegroup' => true, 'changegroupsubset' => true, 'debugwireargs' => true, 'getbundle' => true, 'heads' => true, 'hello' => true, 'known' => true, 'listkeys' => true, 'lookup' => true, 'stream_out' => true, ); // Notably, the write commands are "pushkey" and "unbundle". The // "batch" command is theoretically read only, but we require explicit // analysis of the actual commands. return isset($read_only[$command]); } public static function isReadOnlyBatchCommand($cmds) { if (!strlen($cmds)) { // We expect a "batch" command to always have a "cmds" string, so err // on the side of caution and throw if we don't get any data here. This // either indicates a mangled command from the client or a programming // error in our code. throw new Exception(pht("Expected nonempty '%s' specification!", 'cmds')); } // For "batch" we get a "cmds" argument like: // // heads ;known nodes= // // We need to examine the commands (here, "heads" and "known") to make sure // they're all read-only. // NOTE: Mercurial has some code to escape semicolons, but it does not // actually function for command separation. For example, these two batch // commands will produce completely different results (the former will run // the lookup; the latter will fail with a parser error): // // lookup key=a:xb;lookup key=z* 0 // lookup key=a:;b;lookup key=z* 0 // ^ // | // +-- Note semicolon. // // So just split unconditionally. $cmds = explode(';', $cmds); foreach ($cmds as $sub_cmd) { $name = head(explode(' ', $sub_cmd, 2)); if (!self::isReadOnlyCommand($name)) { return false; } } return true; } /** If the server version is running 3.4+ it will respond * with 'bundle2' capability in the format of "bundle2=(url-encoding)". * Until we maange to properly package up bundles to send back we * disallow the client from knowing we speak bundle2 by removing it * from the capabilities listing. * - * The format of the capabilties string is: "a space separated list + * The format of the capabilities string is: "a space separated list * of strings representing what commands the server supports" * @link https://www.mercurial-scm.org/wiki/CommandServer#Protocol * * @param string $capabilities - The string of capabilities to * strip the bundle2 capability from. This is expected to be * the space-separated list of strings resulting from the - * querying the 'capabilties' command. + * querying the 'capabilities' command. * * @return string The resulting space-separated list of capabilities * which no longer contains the 'bundle2' capability. This is meant * to replace the original $body to send back to client. */ public static function filterBundle2Capability($capabilities) { $parts = explode(' ', $capabilities); foreach ($parts as $key => $part) { if (preg_match('/^bundle2=/', $part)) { unset($parts[$key]); break; } } return implode(' ', $parts); } } diff --git a/src/applications/policy/controller/PhabricatorPolicyExplainController.php b/src/applications/policy/controller/PhabricatorPolicyExplainController.php index da4103765b..fc508cfa3a 100644 --- a/src/applications/policy/controller/PhabricatorPolicyExplainController.php +++ b/src/applications/policy/controller/PhabricatorPolicyExplainController.php @@ -1,326 +1,326 @@ getViewer(); $phid = $request->getURIData('phid'); $capability = $request->getURIData('capability'); $object = id(new PhabricatorObjectQuery()) ->setViewer($viewer) ->withPHIDs(array($phid)) ->executeOne(); if (!$object) { return new Aphront404Response(); } $policies = PhabricatorPolicyQuery::loadPolicies( $viewer, $object); $policy = idx($policies, $capability); if (!$policy) { return new Aphront404Response(); } $handle = id(new PhabricatorHandleQuery()) ->setViewer($viewer) ->withPHIDs(array($phid)) ->executeOne(); $object_name = $handle->getName(); $object_uri = nonempty($handle->getURI(), '/'); $dialog = id(new AphrontDialogView()) ->setUser($viewer) ->setClass('aphront-access-dialog aphront-policy-explain-dialog') ->setTitle(pht('Policy Details: %s', $object_name)) ->addCancelButton($object_uri, pht('Done')); $space_section = $this->buildSpaceSection( $object, $policy, $capability); $extended_section = $this->buildExtendedSection( $object, $capability); $exceptions_section = $this->buildExceptionsSection( $object, $capability); $object_section = $this->buildObjectSection( $object, $policy, $capability, $handle); $dialog->appendChild( array( $space_section, $extended_section, $exceptions_section, $object_section, )); return $dialog; } private function buildSpaceSection( PhabricatorPolicyInterface $object, PhabricatorPolicy $policy, $capability) { $viewer = $this->getViewer(); if (!($object instanceof PhabricatorSpacesInterface)) { return null; } if (!PhabricatorSpacesNamespaceQuery::getSpacesExist()) { return null; } $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID( $object); $spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($viewer); $space = idx($spaces, $space_phid); if (!$space) { return null; } $space_policies = PhabricatorPolicyQuery::loadPolicies($viewer, $space); $space_policy = idx($space_policies, PhabricatorPolicyCapability::CAN_VIEW); if (!$space_policy) { return null; } $doc_href = PhabricatorEnv::getDoclink('Spaces User Guide'); $capability_name = $this->getCapabilityName($capability); $space_section = id(new PHUIPolicySectionView()) ->setViewer($viewer) ->setIcon('fa-th-large bluegrey') ->setHeader(pht('Space')) ->setDocumentationLink(pht('Spaces Documentation'), $doc_href) ->appendList( array( array( phutil_tag('strong', array(), pht('Space:')), ' ', $viewer->renderHandle($space_phid)->setAsTag(true), ), array( phutil_tag('strong', array(), pht('%s:', $capability_name)), ' ', $space_policy->getShortName(), ), )) ->appendParagraph( pht( 'This object is in %s and can only be seen or edited by users '. 'with access to view objects in the space.', $viewer->renderHandle($space_phid))); $space_explanation = PhabricatorPolicy::getPolicyExplanation( $viewer, $space_policy->getPHID()); $items = array(); $items[] = $space_explanation; $space_section ->appendParagraph(pht('Users who can see objects in this space:')) ->appendList($items); $view_capability = PhabricatorPolicyCapability::CAN_VIEW; if ($capability == $view_capability) { $stronger = $space_policy->isStrongerThan($policy); if ($stronger) { $space_section->appendHint( pht( 'The space this object is in has a more restrictive view '. 'policy ("%s") than the object does ("%s"), so the space\'s '. 'view policy is shown as a hint instead of the object policy.', $space_policy->getShortName(), $policy->getShortName())); } } $space_section->appendHint( pht( 'After a user passes space policy checks, they must still pass '. 'object policy checks.')); return $space_section; } private function getStrengthInformation( PhabricatorPolicyInterface $object, PhabricatorPolicy $policy, $capability) { $viewer = $this->getViewer(); $default_policy = PhabricatorPolicyQuery::getDefaultPolicyForObject( $viewer, $object, $capability); if (!$default_policy) { return; } if ($default_policy->getPHID() == $policy->getPHID()) { return; } if ($default_policy->isStrongerThan($policy)) { $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)) { $info = pht( 'This object has a more restrictive policy ("%s") than the default '. 'policy for similar objects (which is "%s").', $policy->getShortName(), $default_policy->getShortName()); } else { $info = pht( 'This object has a different policy ("%s") than the default policy '. 'for similar objects (which is "%s").', $policy->getShortName(), $default_policy->getShortName()); } return $info; } private function getCapabilityName($capability) { $capability_name = $capability; $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability); if ($capobj) { $capability_name = $capobj->getCapabilityName(); } return $capability_name; } private function buildExtendedSection( PhabricatorPolicyInterface $object, $capability) { $viewer = $this->getViewer(); if (!($object instanceof PhabricatorExtendedPolicyInterface)) { return null; } $extended_rules = $object->getExtendedPolicy($capability, $viewer); if (!$extended_rules) { return null; } $items = array(); foreach ($extended_rules as $extended_rule) { $extended_target = $extended_rule[0]; $extended_capabilities = (array)$extended_rule[1]; if (is_object($extended_target)) { $extended_target = $extended_target->getPHID(); } foreach ($extended_capabilities as $extended_capability) { $ex_name = $this->getCapabilityName($extended_capability); $items[] = array( phutil_tag('strong', array(), pht('%s:', $ex_name)), ' ', $viewer->renderHandle($extended_target)->setAsTag(true), ); } } return id(new PHUIPolicySectionView()) ->setViewer($viewer) ->setIcon('fa-link') ->setHeader(pht('Required Capabilities on Other Objects')) ->appendParagraph( pht( 'To access this object, users must have first have access '. - 'capabilties on these other objects:')) + 'capabilities on these other objects:')) ->appendList($items); } private function buildExceptionsSection( PhabricatorPolicyInterface $object, $capability) { $viewer = $this->getViewer(); $exceptions = PhabricatorPolicy::getSpecialRules( $object, $viewer, $capability, false); if (!$exceptions) { return null; } return id(new PHUIPolicySectionView()) ->setViewer($viewer) ->setIcon('fa-unlock-alt red') ->setHeader(pht('Special Rules')) ->appendParagraph( pht( 'This object has special rules which override normal object '. 'policy rules:')) ->appendList($exceptions); } private function buildObjectSection( PhabricatorPolicyInterface $object, PhabricatorPolicy $policy, $capability, PhabricatorObjectHandle $handle) { $viewer = $this->getViewer(); $capability_name = $this->getCapabilityName($capability); $object_section = id(new PHUIPolicySectionView()) ->setViewer($viewer) ->setIcon($handle->getIcon().' bluegrey') ->setHeader(pht('Object Policy')) ->appendList( array( array( phutil_tag('strong', array(), pht('%s:', $capability_name)), ' ', $policy->getShortName(), ), )) ->appendParagraph( pht( 'In detail, this means that these users can take this action, '. 'provided they pass all of the checks described above first:')) ->appendList( array( PhabricatorPolicy::getPolicyExplanation( $viewer, $policy->getPHID()), )); $strength = $this->getStrengthInformation($object, $policy, $capability); if ($strength) { $object_section->appendHint($strength); } return $object_section; } }