Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2893251
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
20 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment