Changeset View
Changeset View
Standalone View
Standalone View
src/applications/policy/filter/PhabricatorPolicyFilter.php
Show First 20 Lines • Show All 190 Lines • ▼ Show 20 Lines | if ($need_projects) { | ||||
->execute(); | ->execute(); | ||||
foreach ($projects as $project) { | foreach ($projects as $project) { | ||||
$this->userProjects[$viewer_phid][$project->getPHID()] = true; | $this->userProjects[$viewer_phid][$project->getPHID()] = true; | ||||
} | } | ||||
} | } | ||||
foreach ($objects as $key => $object) { | foreach ($objects as $key => $object) { | ||||
$object_capabilities = $object->getCapabilities(); | |||||
foreach ($capabilities as $capability) { | foreach ($capabilities as $capability) { | ||||
if (!$this->checkCapability($object, $capability)) { | if (!$this->checkCapability($object, $capability)) { | ||||
// If we're missing any capability, move on to the next object. | // If we're missing any capability, move on to the next object. | ||||
continue 2; | continue 2; | ||||
} | } | ||||
} | } | ||||
// If we make it here, we have all of the required capabilities. | // If we make it here, we have all of the required capabilities. | ||||
$filtered[$key] = $object; | $filtered[$key] = $object; | ||||
} | } | ||||
return $filtered; | // If we survied the primary checks, apply extended checks to objects | ||||
// with extended policies. | |||||
$results = array(); | |||||
$extended = array(); | |||||
foreach ($filtered as $key => $object) { | |||||
if ($object instanceof PhabricatorExtendedPolicyInterface) { | |||||
$extended[$key] = $object; | |||||
} else { | |||||
$results[$key] = $object; | |||||
} | |||||
} | |||||
if ($extended) { | |||||
$results += $this->applyExtendedPolicyChecks($extended); | |||||
// Put results back in the original order. | |||||
$results = array_select_keys($results, array_keys($filtered)); | |||||
} | |||||
return $results; | |||||
} | |||||
private function applyExtendedPolicyChecks(array $extended_objects) { | |||||
// First, we're going to detect cycles and reject any objects which are | |||||
// part of a cycle. We don't want to loop forever if an object has a | |||||
// self-referential or nonsense policy. | |||||
static $in_flight = array(); | |||||
$all_phids = array(); | |||||
foreach ($extended_objects as $key => $object) { | |||||
$phid = $object->getPHID(); | |||||
if (isset($in_flight[$phid])) { | |||||
// TODO: This could be more user-friendly. | |||||
$this->rejectObject($extended_objects[$key], false, '<cycle>'); | |||||
unset($extended_objects[$key]); | |||||
continue; | |||||
} | |||||
// We might throw from rejectObject(), so we don't want to actually mark | |||||
// anything as in-flight until we survive this entire step. | |||||
$all_phids[$phid] = $phid; | |||||
} | |||||
foreach ($all_phids as $phid) { | |||||
$in_flight[$phid] = true; | |||||
} | |||||
$caught = null; | |||||
try { | |||||
$extended_objects = $this->executeExtendedPolicyChecks($extended_objects); | |||||
} catch (Exception $ex) { | |||||
$caught = $ex; | |||||
} | |||||
foreach ($all_phids as $phid) { | |||||
unset($in_flight[$phid]); | |||||
} | |||||
if ($caught) { | |||||
throw $caught; | |||||
} | |||||
return $extended_objects; | |||||
} | |||||
private function executeExtendedPolicyChecks(array $extended_objects) { | |||||
$viewer = $this->viewer; | |||||
$filter_capabilities = $this->capabilities; | |||||
// Iterate over the objects we need to filter and pull all the nonempty | |||||
// policies into a flat, structured list. | |||||
$all_structs = array(); | |||||
foreach ($extended_objects as $key => $extended_object) { | |||||
foreach ($filter_capabilities as $extended_capability) { | |||||
$extended_policies = $extended_object->getExtendedPolicy( | |||||
$extended_capability, | |||||
$viewer); | |||||
if (!$extended_policies) { | |||||
continue; | |||||
} | |||||
foreach ($extended_policies as $extended_policy) { | |||||
list($object, $capabilities) = $extended_policy; | |||||
// Build a description of the capabilities we need to check. This | |||||
// will be something like `"view"`, or `"edit view"`, or possibly | |||||
// a longer string with custom capabilities. Later, group the objects | |||||
// up into groups which need the same capabilities tested. | |||||
$capabilities = (array)$capabilities; | |||||
$capabilities = array_fuse($capabilities); | |||||
ksort($capabilities); | |||||
$group = implode(' ', $capabilities); | |||||
$struct = array( | |||||
'key' => $key, | |||||
'for' => $extended_capability, | |||||
'object' => $object, | |||||
'capabilities' => $capabilities, | |||||
'group' => $group, | |||||
); | |||||
$all_structs[] = $struct; | |||||
} | |||||
} | |||||
} | |||||
// Extract any bare PHIDs from the structs; we need to load these objects. | |||||
// These are objects which are required in order to perform an extended | |||||
// policy check but which the original viewer did not have permission to | |||||
// see (they presumably had other permissions which let them load the | |||||
// object in the first place). | |||||
$all_phids = array(); | |||||
foreach ($all_structs as $idx => $struct) { | |||||
$object = $struct['object']; | |||||
if (is_string($object)) { | |||||
$all_phids[$object] = $object; | |||||
} | |||||
} | |||||
// If we have some bare PHIDs, we need to load the corresponding objects. | |||||
if ($all_phids) { | |||||
// We can pull these with the omnipotent user because we're immediately | |||||
// filtering them. | |||||
$ref_objects = id(new PhabricatorObjectQuery()) | |||||
->setViewer(PhabricatorUser::getOmnipotentUser()) | |||||
->withPHIDs($all_phids) | |||||
->execute(); | |||||
$ref_objects = mpull($ref_objects, null, 'getPHID'); | |||||
} else { | |||||
$ref_objects = array(); | |||||
} | |||||
// Group the list of checks by the capabilities we need to check. | |||||
$groups = igroup($all_structs, 'group'); | |||||
foreach ($groups as $structs) { | |||||
$head = head($structs); | |||||
// All of the items in each group are checking for the same capabilities. | |||||
$capabilities = $head['capabilities']; | |||||
$key_map = array(); | |||||
$objects_in = array(); | |||||
foreach ($structs as $struct) { | |||||
$extended_key = $struct['key']; | |||||
if (empty($extended_objects[$key])) { | |||||
// If this object has already been rejected by an earlier filtering | |||||
// pass, we don't need to do any tests on it. | |||||
continue; | |||||
} | |||||
$object = $struct['object']; | |||||
if (is_string($object)) { | |||||
// This is really a PHID, so look it up. | |||||
$object_phid = $object; | |||||
if (empty($ref_objects[$object_phid])) { | |||||
// We weren't able to load the corresponding object, so just | |||||
// reject this result outright. | |||||
$reject = $extended_objects[$key]; | |||||
unset($extended_objects[$key]); | |||||
// TODO: This could be friendlier. | |||||
$this->rejectObject($reject, false, '<bad-ref>'); | |||||
continue; | |||||
} | |||||
$object = $ref_objects[$object_phid]; | |||||
} | |||||
$phid = $object->getPHID(); | |||||
$key_map[$phid][] = $extended_key; | |||||
$objects_in[$phid] = $object; | |||||
} | |||||
if ($objects_in) { | |||||
$objects_out = id(new PhabricatorPolicyFilter()) | |||||
->setViewer($viewer) | |||||
->requireCapabilities($capabilities) | |||||
->apply($objects_in); | |||||
$objects_out = mpull($objects_out, null, 'getPHID'); | |||||
} else { | |||||
$objects_out = array(); | |||||
} | |||||
// If any objects were removed by filtering, we're going to reject all | |||||
// of the original objects which needed them. | |||||
foreach ($objects_in as $phid => $object_in) { | |||||
if (isset($objects_out[$phid])) { | |||||
// This object survived filtering, so we don't need to throw any | |||||
// results away. | |||||
continue; | |||||
} | |||||
foreach ($key_map[$phid] as $extended_key) { | |||||
if (empty($extended_objects[$extended_key])) { | |||||
// We've already rejected this object, so we don't need to reject | |||||
// it again. | |||||
continue; | |||||
} | |||||
$reject = $extended_objects[$extended_key]; | |||||
unset($extended_objects[$extended_key]); | |||||
// TODO: This isn't as user-friendly as it could be. It's possible | |||||
// that we're rejecting this object for multiple capability/policy | |||||
// failures, though. | |||||
$this->rejectObject($reject, false, '<extended>'); | |||||
} | |||||
} | |||||
} | |||||
return $extended_objects; | |||||
} | } | ||||
private function checkCapability( | private function checkCapability( | ||||
PhabricatorPolicyInterface $object, | PhabricatorPolicyInterface $object, | ||||
$capability) { | $capability) { | ||||
$policy = $this->getObjectPolicy($object, $capability); | $policy = $this->getObjectPolicy($object, $capability); | ||||
▲ Show 20 Lines • Show All 242 Lines • Show Last 20 Lines |