Page MenuHomePhorge

No OneTemporary

diff --git a/src/applications/policy/__tests__/PhabricatorPolicyTestCase.php b/src/applications/policy/__tests__/PhabricatorPolicyTestCase.php
index aff5ee7d6e..b8b0236b52 100644
--- a/src/applications/policy/__tests__/PhabricatorPolicyTestCase.php
+++ b/src/applications/policy/__tests__/PhabricatorPolicyTestCase.php
@@ -1,253 +1,292 @@
<?php
final class PhabricatorPolicyTestCase extends PhabricatorTestCase {
/**
* Verify that any user can view an object with POLICY_PUBLIC.
*/
public function testPublicPolicyEnabled() {
$env = PhabricatorEnv::beginScopedEnv();
$env->overrideEnvConfig('policy.allow-public', true);
$this->expectVisibility(
$this->buildObject(PhabricatorPolicies::POLICY_PUBLIC),
array(
'public' => true,
'user' => true,
'admin' => true,
),
'Public Policy (Enabled in Config)');
}
/**
* Verify that POLICY_PUBLIC is interpreted as POLICY_USER when public
* policies are disallowed.
*/
public function testPublicPolicyDisabled() {
$env = PhabricatorEnv::beginScopedEnv();
$env->overrideEnvConfig('policy.allow-public', false);
$this->expectVisibility(
$this->buildObject(PhabricatorPolicies::POLICY_PUBLIC),
array(
'public' => false,
'user' => true,
'admin' => true,
),
'Public Policy (Disabled in Config)');
}
/**
* Verify that any logged-in user can view an object with POLICY_USER, but
* logged-out users can not.
*/
public function testUsersPolicy() {
$this->expectVisibility(
$this->buildObject(PhabricatorPolicies::POLICY_USER),
array(
'public' => false,
'user' => true,
'admin' => true,
),
'User Policy');
}
/**
* Verify that only administrators can view an object with POLICY_ADMIN.
*/
public function testAdminPolicy() {
$this->expectVisibility(
$this->buildObject(PhabricatorPolicies::POLICY_ADMIN),
array(
'public' => false,
'user' => false,
'admin' => true,
),
'Admin Policy');
}
/**
* Verify that no one can view an object with POLICY_NOONE.
*/
public function testNoOnePolicy() {
$this->expectVisibility(
$this->buildObject(PhabricatorPolicies::POLICY_NOONE),
array(
'public' => false,
'user' => false,
'admin' => false,
),
'No One Policy');
-
}
/**
* Test offset-based filtering.
*/
public function testOffsets() {
$results = array(
$this->buildObject(PhabricatorPolicies::POLICY_NOONE),
$this->buildObject(PhabricatorPolicies::POLICY_NOONE),
$this->buildObject(PhabricatorPolicies::POLICY_NOONE),
$this->buildObject(PhabricatorPolicies::POLICY_USER),
$this->buildObject(PhabricatorPolicies::POLICY_USER),
$this->buildObject(PhabricatorPolicies::POLICY_USER),
);
$query = new PhabricatorPolicyAwareTestQuery();
$query->setResults($results);
$query->setViewer($this->buildUser('user'));
$this->assertEqual(
3,
count($query->setLimit(3)->setOffset(0)->execute()),
'Invisible objects are ignored.');
$this->assertEqual(
0,
count($query->setLimit(3)->setOffset(3)->execute()),
'Offset pages through visible objects only.');
$this->assertEqual(
2,
count($query->setLimit(3)->setOffset(1)->execute()),
'Offsets work correctly.');
$this->assertEqual(
2,
count($query->setLimit(0)->setOffset(1)->execute()),
'Offset with no limit works.');
}
/**
* Test limits.
*/
public function testLimits() {
$results = array(
$this->buildObject(PhabricatorPolicies::POLICY_USER),
$this->buildObject(PhabricatorPolicies::POLICY_USER),
$this->buildObject(PhabricatorPolicies::POLICY_USER),
$this->buildObject(PhabricatorPolicies::POLICY_USER),
$this->buildObject(PhabricatorPolicies::POLICY_USER),
$this->buildObject(PhabricatorPolicies::POLICY_USER),
);
$query = new PhabricatorPolicyAwareTestQuery();
$query->setResults($results);
$query->setViewer($this->buildUser('user'));
$this->assertEqual(
3,
count($query->setLimit(3)->setOffset(0)->execute()),
'Limits work.');
$this->assertEqual(
2,
count($query->setLimit(3)->setOffset(4)->execute()),
'Limit + offset work.');
}
/**
* Test that omnipotent users bypass policies.
*/
public function testOmnipotence() {
$results = array(
$this->buildObject(PhabricatorPolicies::POLICY_NOONE),
);
$query = new PhabricatorPolicyAwareTestQuery();
$query->setResults($results);
$query->setViewer(PhabricatorUser::getOmnipotentUser());
$this->assertEqual(
1,
count($query->execute()));
}
+ /**
+ * Test that invalid policies reject viewers of all types.
+ */
+ public function testRejectInvalidPolicy() {
+ $invalid_policy = "the duck goes quack";
+ $object = $this->buildObject($invalid_policy);
+
+ $this->expectVisibility(
+ $object = $this->buildObject($invalid_policy),
+ array(
+ 'public' => false,
+ 'user' => false,
+ 'admin' => false,
+ ),
+ 'Invalid Policy');
+ }
+
+
+ /**
+ * An omnipotent user should be able to see even objects with invalid
+ * policies.
+ */
+ public function testInvalidPolicyVisibleByOmnipotentUser() {
+ $invalid_policy = "the cow goes moo";
+ $object = $this->buildObject($invalid_policy);
+
+ $results = array(
+ $object,
+ );
+
+ $query = new PhabricatorPolicyAwareTestQuery();
+ $query->setResults($results);
+ $query->setViewer(PhabricatorUser::getOmnipotentUser());
+
+ $this->assertEqual(
+ 1,
+ count($query->execute()));
+ }
+
+
/**
* Test an object for visibility across multiple user specifications.
*/
private function expectVisibility(
PhabricatorPolicyTestObject $object,
array $map,
$description) {
foreach ($map as $spec => $expect) {
$viewer = $this->buildUser($spec);
$query = new PhabricatorPolicyAwareTestQuery();
$query->setResults(array($object));
$query->setViewer($viewer);
$caught = null;
try {
$result = $query->executeOne();
} catch (PhabricatorPolicyException $ex) {
$caught = $ex;
}
if ($expect) {
$this->assertEqual(
$object,
$result,
"{$description} with user {$spec} should succeed.");
} else {
$this->assertEqual(
true,
$caught instanceof PhabricatorPolicyException,
"{$description} with user {$spec} should fail.");
}
}
}
/**
* Build a test object to spec.
*/
private function buildObject($policy) {
$object = new PhabricatorPolicyTestObject();
$object->setCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
));
$object->setPolicies(
array(
PhabricatorPolicyCapability::CAN_VIEW => $policy,
));
return $object;
}
/**
* Build a test user to spec.
*/
private function buildUser($spec) {
$user = new PhabricatorUser();
switch ($spec) {
case 'public':
break;
case 'user':
$user->setPHID(1);
break;
case 'admin':
$user->setPHID(1);
$user->setIsAdmin(true);
break;
default:
throw new Exception("Unknown user spec '{$spec}'.");
}
return $user;
}
}
diff --git a/src/applications/policy/filter/PhabricatorPolicyFilter.php b/src/applications/policy/filter/PhabricatorPolicyFilter.php
index b5907e6a7a..1d6bba9c0a 100644
--- a/src/applications/policy/filter/PhabricatorPolicyFilter.php
+++ b/src/applications/policy/filter/PhabricatorPolicyFilter.php
@@ -1,369 +1,380 @@
<?php
final class PhabricatorPolicyFilter {
private $viewer;
private $objects;
private $capabilities;
private $raisePolicyExceptions;
private $userProjects;
public static function mustRetainCapability(
PhabricatorUser $user,
PhabricatorPolicyInterface $object,
$capability) {
if (!self::hasCapability($user, $object, $capability)) {
throw new Exception(
"You can not make that edit, because it would remove your ability ".
"to '{$capability}' the object.");
}
}
public static function requireCapability(
PhabricatorUser $user,
PhabricatorPolicyInterface $object,
$capability) {
$filter = new PhabricatorPolicyFilter();
$filter->setViewer($user);
$filter->requireCapabilities(array($capability));
$filter->raisePolicyExceptions(true);
$filter->apply(array($object));
}
public static function hasCapability(
PhabricatorUser $user,
PhabricatorPolicyInterface $object,
$capability) {
$filter = new PhabricatorPolicyFilter();
$filter->setViewer($user);
$filter->requireCapabilities(array($capability));
$result = $filter->apply(array($object));
return (count($result) == 1);
}
public function setViewer(PhabricatorUser $user) {
$this->viewer = $user;
return $this;
}
public function requireCapabilities(array $capabilities) {
$this->capabilities = $capabilities;
return $this;
}
public function raisePolicyExceptions($raise) {
$this->raisePolicyExceptions = $raise;
return $this;
}
public function apply(array $objects) {
assert_instances_of($objects, 'PhabricatorPolicyInterface');
$viewer = $this->viewer;
$capabilities = $this->capabilities;
if (!$viewer || !$capabilities) {
throw new Exception(
'Call setViewer() and requireCapabilities() before apply()!');
}
// If the viewer is omnipotent, short circuit all the checks and just
// return the input unmodified. This is an optimization; we know the
// result already.
if ($viewer->isOmnipotent()) {
return $objects;
}
$filtered = array();
$viewer_phid = $viewer->getPHID();
if (empty($this->userProjects[$viewer_phid])) {
$this->userProjects[$viewer_phid] = array();
}
$need_projects = array();
foreach ($objects as $key => $object) {
$object_capabilities = $object->getCapabilities();
foreach ($capabilities as $capability) {
if (!in_array($capability, $object_capabilities)) {
throw new Exception(
"Testing for capability '{$capability}' on an object which does ".
"not have that capability!");
}
$policy = $object->getPolicy($capability);
$type = phid_get_type($policy);
if ($type == PhabricatorProjectPHIDTypeProject::TYPECONST) {
$need_projects[$policy] = $policy;
}
}
}
// If we need projects, check if any of the projects we need are also the
// objects we're filtering. Because of how project rules work, this is a
// common case.
if ($need_projects) {
foreach ($objects as $object) {
if ($object instanceof PhabricatorProject) {
$project_phid = $object->getPHID();
if (isset($need_projects[$project_phid])) {
$is_member = $object->isUserMember($viewer_phid);
$this->userProjects[$viewer_phid][$project_phid] = $is_member;
unset($need_projects[$project_phid]);
}
}
}
}
if ($need_projects) {
$need_projects = array_unique($need_projects);
// NOTE: We're using the omnipotent user here to avoid a recursive
// descent into madness. We don't actually need to know if the user can
// see these projects or not, since: the check is "user is member of
// project", not "user can see project"; and membership implies
// visibility anyway. Without this, we may load other projects and
// re-enter the policy filter and generally create a huge mess.
$projects = id(new PhabricatorProjectQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withMemberPHIDs(array($viewer->getPHID()))
->withPHIDs($need_projects)
->execute();
foreach ($projects as $project) {
$this->userProjects[$viewer_phid][$project->getPHID()] = true;
}
}
foreach ($objects as $key => $object) {
$object_capabilities = $object->getCapabilities();
foreach ($capabilities as $capability) {
if (!$this->checkCapability($object, $capability)) {
// If we're missing any capability, move on to the next object.
continue 2;
}
// If we make it here, we have all of the required capabilities.
$filtered[$key] = $object;
}
}
return $filtered;
}
private function checkCapability(
PhabricatorPolicyInterface $object,
$capability) {
$policy = $object->getPolicy($capability);
if (!$policy) {
// TODO: Formalize this somehow?
$policy = PhabricatorPolicies::POLICY_USER;
}
if ($policy == PhabricatorPolicies::POLICY_PUBLIC) {
// If the object is set to "public" but that policy is disabled for this
// install, restrict the policy to "user".
if (!PhabricatorEnv::getEnvConfig('policy.allow-public')) {
$policy = PhabricatorPolicies::POLICY_USER;
}
// If the object is set to "public" but the capability is anything other
// than "view", restrict the policy to "user".
if ($capability != PhabricatorPolicyCapability::CAN_VIEW) {
$policy = PhabricatorPolicies::POLICY_USER;
}
}
$viewer = $this->viewer;
if ($viewer->isOmnipotent()) {
return true;
}
if ($object->hasAutomaticCapability($capability, $viewer)) {
return true;
}
switch ($policy) {
case PhabricatorPolicies::POLICY_PUBLIC:
return true;
case PhabricatorPolicies::POLICY_USER:
if ($viewer->getPHID()) {
return true;
} else {
$this->rejectObject($object, $policy, $capability);
}
break;
case PhabricatorPolicies::POLICY_ADMIN:
if ($viewer->getIsAdmin()) {
return true;
} else {
$this->rejectObject($object, $policy, $capability);
}
break;
case PhabricatorPolicies::POLICY_NOONE:
$this->rejectObject($object, $policy, $capability);
break;
default:
$type = phid_get_type($policy);
if ($type == PhabricatorProjectPHIDTypeProject::TYPECONST) {
if (!empty($this->userProjects[$viewer->getPHID()][$policy])) {
return true;
} else {
$this->rejectObject($object, $policy, $capability);
}
} else if ($type == PhabricatorPeoplePHIDTypeUser::TYPECONST) {
if ($viewer->getPHID() == $policy) {
return true;
} else {
$this->rejectObject($object, $policy, $capability);
}
} else {
- throw new Exception("Object has unknown policy '{$policy}'!");
+ // Reject objects with unknown policies.
+ $this->rejectObject($object, false, $capability);
}
}
return false;
}
private function rejectImpossiblePolicy(
PhabricatorPolicyInterface $object,
$policy,
$capability) {
if (!$this->raisePolicyExceptions) {
return;
}
- // TODO: clean this up
- $verb = $capability;
+ switch ($capability) {
+ case PhabricatorPolicyCapability::CAN_VIEW:
+ $message = pht("This object has an impossible view policy.");
+ break;
+ case PhabricatorPolicyCapability::CAN_EDIT:
+ $message = pht("This object has an impossible edit policy.");
+ break;
+ case PhabricatorPolicyCapability::CAN_JOIN:
+ $message = pht("This object has an impossible join policy.");
+ break;
+ default:
+ $message = pht("This object has an impossible policy.");
+ break;
+ }
- throw new PhabricatorPolicyException(
- "This object has an impossible {$verb} policy.");
+ throw new PhabricatorPolicyException($message);
}
public function rejectObject(
PhabricatorPolicyInterface $object,
$policy,
$capability) {
if (!$this->raisePolicyExceptions) {
return;
}
$more = array();
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
$message = pht(
'This object exists, but you do not have permission to view it.');
break;
case PhabricatorPolicyCapability::CAN_EDIT:
$message = pht('You do not have permission to edit this object.');
break;
case PhabricatorPolicyCapability::CAN_JOIN:
$message = pht('You do not have permission to join this object.');
break;
}
switch ($policy) {
case PhabricatorPolicies::POLICY_PUBLIC:
// Presumably, this is a bug, so we don't bother specializing the
// strings.
$more = pht('This object is public.');
break;
case PhabricatorPolicies::POLICY_USER:
// We always raise this as "log in", so we don't need to specialize.
$more = pht('This object is available to logged in users.');
break;
case PhabricatorPolicies::POLICY_ADMIN:
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
$more = pht('Administrators can view this object.');
break;
case PhabricatorPolicyCapability::CAN_EDIT:
$more = pht('Administrators can edit this object.');
break;
case PhabricatorPolicyCapability::CAN_JOIN:
$more = pht('Administrators can join this object.');
break;
}
break;
case PhabricatorPolicies::POLICY_NOONE:
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
$more = pht('By default, no one can view this object.');
break;
case PhabricatorPolicyCapability::CAN_EDIT:
$more = pht('By default, no one can edit this object.');
break;
case PhabricatorPolicyCapability::CAN_JOIN:
$more = pht('By default, no one can join this object.');
break;
}
break;
default:
$handle = id(new PhabricatorHandleQuery())
->setViewer($this->viewer)
->withPHIDs(array($policy))
->executeOne();
$type = phid_get_type($policy);
if ($type == PhabricatorProjectPHIDTypeProject::TYPECONST) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
$more = pht(
'This object is visible to members of the project "%s".',
$handle->getFullName());
break;
case PhabricatorPolicyCapability::CAN_EDIT:
$more = pht(
'This object can be edited by members of the project "%s".',
$handle->getFullName());
break;
case PhabricatorPolicyCapability::CAN_JOIN:
$more = pht(
'This object can be joined by members of the project "%s".',
$handle->getFullName());
break;
}
} else if ($type == PhabricatorPeoplePHIDTypeUser::TYPECONST) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
$more = pht(
'%s can view this object.',
$handle->getFullName());
break;
case PhabricatorPolicyCapability::CAN_EDIT:
$more = pht(
'%s can edit this object.',
$handle->getFullName());
break;
case PhabricatorPolicyCapability::CAN_JOIN:
$more = pht(
'%s can join this object.',
$handle->getFullName());
break;
}
-
} else {
- $who = pht("This object has an unknown policy setting.");
+ $who = pht("This object has an unknown or invalid policy setting.");
}
break;
}
$more = array_merge(
array($more),
array_filter((array)$object->describeAutomaticCapability($capability)));
$exception = new PhabricatorPolicyException($message);
$exception->setMoreInfo($more);
throw $exception;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 18:06 (2 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1127260
Default Alt Text
(20 KB)

Event Timeline