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