Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/maniphest/storage/ManiphestTask.php b/src/applications/maniphest/storage/ManiphestTask.php
index b15544429d..171f504b7b 100644
--- a/src/applications/maniphest/storage/ManiphestTask.php
+++ b/src/applications/maniphest/storage/ManiphestTask.php
@@ -1,276 +1,276 @@
<?php
final class ManiphestTask extends ManiphestDAO
implements
PhabricatorMarkupInterface,
PhabricatorPolicyInterface,
PhabricatorTokenReceiverInterface,
PhrequentTrackableInterface,
PhabricatorCustomFieldInterface {
const MARKUP_FIELD_DESCRIPTION = 'markup:desc';
protected $phid;
protected $authorPHID;
protected $ownerPHID;
protected $ccPHIDs = array();
protected $status = ManiphestTaskStatus::STATUS_OPEN;
protected $priority;
protected $subpriority = 0;
protected $title = '';
- protected $originalTitle;
+ protected $originalTitle = '';
protected $description = '';
protected $originalEmailSource;
protected $mailKey;
protected $viewPolicy = PhabricatorPolicies::POLICY_USER;
protected $editPolicy = PhabricatorPolicies::POLICY_USER;
protected $attached = array();
protected $projectPHIDs = array();
private $projectsNeedUpdate;
private $subscribersNeedUpdate;
protected $ownerOrdering;
private $groupByProjectPHID = self::ATTACHABLE;
private $customFields = self::ATTACHABLE;
public static function initializeNewTask(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
->withClasses(array('PhabricatorApplicationManiphest'))
->executeOne();
$view_policy = $app->getPolicy(ManiphestCapabilityDefaultView::CAPABILITY);
$edit_policy = $app->getPolicy(ManiphestCapabilityDefaultEdit::CAPABILITY);
return id(new ManiphestTask())
->setPriority(ManiphestTaskPriority::getDefaultPriority())
->setAuthorPHID($actor->getPHID())
->setViewPolicy($view_policy)
->setEditPolicy($edit_policy);
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'ccPHIDs' => self::SERIALIZATION_JSON,
'attached' => self::SERIALIZATION_JSON,
'projectPHIDs' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function loadDependsOnTaskPHIDs() {
return PhabricatorEdgeQuery::loadDestinationPHIDs(
$this->getPHID(),
PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK);
}
public function loadDependedOnByTaskPHIDs() {
return PhabricatorEdgeQuery::loadDestinationPHIDs(
$this->getPHID(),
PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK);
}
public function getAttachedPHIDs($type) {
return array_keys(idx($this->attached, $type, array()));
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(ManiphestPHIDTypeTask::TYPECONST);
}
public function getCCPHIDs() {
return array_values(nonempty($this->ccPHIDs, array()));
}
public function setProjectPHIDs(array $phids) {
$this->projectPHIDs = array_values($phids);
$this->projectsNeedUpdate = true;
return $this;
}
public function getProjectPHIDs() {
return array_values(nonempty($this->projectPHIDs, array()));
}
public function setCCPHIDs(array $phids) {
$this->ccPHIDs = array_values($phids);
$this->subscribersNeedUpdate = true;
return $this;
}
public function setOwnerPHID($phid) {
$this->ownerPHID = nonempty($phid, null);
$this->subscribersNeedUpdate = true;
return $this;
}
public function setTitle($title) {
$this->title = $title;
if (!$this->getID()) {
$this->originalTitle = $title;
}
return $this;
}
public function attachGroupByProjectPHID($phid) {
$this->groupByProjectPHID = $phid;
return $this;
}
public function getGroupByProjectPHID() {
return $this->assertAttached($this->groupByProjectPHID);
}
public function save() {
if (!$this->mailKey) {
$this->mailKey = Filesystem::readRandomCharacters(20);
}
$result = parent::save();
if ($this->projectsNeedUpdate) {
// If we've changed the project PHIDs for this task, update the link
// table.
ManiphestTaskProject::updateTaskProjects($this);
$this->projectsNeedUpdate = false;
}
if ($this->subscribersNeedUpdate) {
// If we've changed the subscriber PHIDs for this task, update the link
// table.
ManiphestTaskSubscriber::updateTaskSubscribers($this);
$this->subscribersNeedUpdate = false;
}
return $result;
}
/* -( Markup Interface )--------------------------------------------------- */
/**
* @task markup
*/
public function getMarkupFieldKey($field) {
$hash = PhabricatorHash::digest($this->getMarkupText($field));
$id = $this->getID();
return "maniphest:T{$id}:{$field}:{$hash}";
}
/**
* @task markup
*/
public function getMarkupText($field) {
return $this->getDescription();
}
/**
* @task markup
*/
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newManiphestMarkupEngine();
}
/**
* @task markup
*/
public function didMarkupText(
$field,
$output,
PhutilMarkupEngine $engine) {
return $output;
}
/**
* @task markup
*/
public function shouldUseMarkupCache($field) {
return (bool)$this->getID();
}
/* -( Policy Interface )--------------------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
// The owner of a task can always view and edit it.
$owner_phid = $this->getOwnerPHID();
if ($owner_phid) {
$user_phid = $user->getPHID();
if ($user_phid == $owner_phid) {
return true;
}
}
return false;
}
public function describeAutomaticCapability($capability) {
return pht(
'The owner of a task can always view and edit it.');
}
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
public function getUsersToNotifyOfTokenGiven() {
// Sort of ambiguous who this was intended for; just let them both know.
return array_filter(
array_unique(
array(
$this->getAuthorPHID(),
$this->getOwnerPHID(),
)));
}
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
public function getCustomFieldSpecificationForRole($role) {
return PhabricatorEnv::getEnvConfig('maniphest.fields');
}
public function getCustomFieldBaseClass() {
return 'ManiphestCustomField';
}
public function getCustomFields() {
return $this->assertAttached($this->customFields);
}
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
$this->customFields = $fields;
return $this;
}
}
diff --git a/src/applications/policy/__tests__/PhabricatorPolicyDataTestCase.php b/src/applications/policy/__tests__/PhabricatorPolicyDataTestCase.php
index 1544138ec9..81aaff88db 100644
--- a/src/applications/policy/__tests__/PhabricatorPolicyDataTestCase.php
+++ b/src/applications/policy/__tests__/PhabricatorPolicyDataTestCase.php
@@ -1,34 +1,147 @@
<?php
final class PhabricatorPolicyDataTestCase extends PhabricatorTestCase {
protected function getPhabricatorTestCaseConfiguration() {
return array(
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
);
}
public function testProjectPolicyMembership() {
$author = $this->generateNewTestUser();
$proj_a = id(new PhabricatorProject())
->setName('A')
->setAuthorPHID($author->getPHID())
->save();
$proj_b = id(new PhabricatorProject())
->setName('B')
->setAuthorPHID($author->getPHID())
->save();
$proj_a->setViewPolicy($proj_b->getPHID())->save();
$proj_b->setViewPolicy($proj_a->getPHID())->save();
$user = new PhabricatorUser();
$results = id(new PhabricatorProjectQuery())
->setViewer($user)
->execute();
$this->assertEqual(0, count($results));
}
+
+ public function testCustomPolicyRuleUser() {
+ $user_a = $this->generateNewTestUser();
+ $user_b = $this->generateNewTestUser();
+ $author = $this->generateNewTestUser();
+
+ $policy = id(new PhabricatorPolicy())
+ ->setRules(
+ array(
+ array(
+ 'action' => PhabricatorPolicy::ACTION_ACCEPT,
+ 'rule' => 'PhabricatorPolicyRuleUsers',
+ 'value' => array($user_a->getPHID()),
+ ),
+ ))
+ ->save();
+
+ $task = ManiphestTask::initializeNewTask($author);
+ $task->setViewPolicy($policy->getPHID());
+ $task->save();
+
+ $can_a_view = PhabricatorPolicyFilter::hasCapability(
+ $user_a,
+ $task,
+ PhabricatorPolicyCapability::CAN_VIEW);
+
+ $this->assertEqual(true, $can_a_view);
+
+ $can_b_view = PhabricatorPolicyFilter::hasCapability(
+ $user_b,
+ $task,
+ PhabricatorPolicyCapability::CAN_VIEW);
+
+ $this->assertEqual(false, $can_b_view);
+ }
+
+ public function testCustomPolicyRuleAdministrators() {
+ $user_a = $this->generateNewTestUser();
+ $user_a->setIsAdmin(true)->save();
+ $user_b = $this->generateNewTestUser();
+ $author = $this->generateNewTestUser();
+
+ $policy = id(new PhabricatorPolicy())
+ ->setRules(
+ array(
+ array(
+ 'action' => PhabricatorPolicy::ACTION_ACCEPT,
+ 'rule' => 'PhabricatorPolicyRuleAdministrators',
+ 'value' => null,
+ ),
+ ))
+ ->save();
+
+ $task = ManiphestTask::initializeNewTask($author);
+ $task->setViewPolicy($policy->getPHID());
+ $task->save();
+
+ $can_a_view = PhabricatorPolicyFilter::hasCapability(
+ $user_a,
+ $task,
+ PhabricatorPolicyCapability::CAN_VIEW);
+
+ $this->assertEqual(true, $can_a_view);
+
+ $can_b_view = PhabricatorPolicyFilter::hasCapability(
+ $user_b,
+ $task,
+ PhabricatorPolicyCapability::CAN_VIEW);
+
+ $this->assertEqual(false, $can_b_view);
+ }
+
+ public function testCustomPolicyRuleLunarPhase() {
+ $user_a = $this->generateNewTestUser();
+ $author = $this->generateNewTestUser();
+
+ $policy = id(new PhabricatorPolicy())
+ ->setRules(
+ array(
+ array(
+ 'action' => PhabricatorPolicy::ACTION_ACCEPT,
+ 'rule' => 'PhabricatorPolicyRuleLunarPhase',
+ 'value' => 'new',
+ ),
+ ))
+ ->save();
+
+ $task = ManiphestTask::initializeNewTask($author);
+ $task->setViewPolicy($policy->getPHID());
+ $task->save();
+
+ $time_a = PhabricatorTime::pushTime(934354800, 'UTC');
+
+ $can_a_view = PhabricatorPolicyFilter::hasCapability(
+ $user_a,
+ $task,
+ PhabricatorPolicyCapability::CAN_VIEW);
+ $this->assertEqual(true, $can_a_view);
+
+ unset($time_a);
+
+
+ $time_b = PhabricatorTime::pushTime(1116745200, 'UTC');
+
+ $can_a_view = PhabricatorPolicyFilter::hasCapability(
+ $user_a,
+ $task,
+ PhabricatorPolicyCapability::CAN_VIEW);
+ $this->assertEqual(false, $can_a_view);
+
+ unset($time_b);
+ }
+
}
diff --git a/src/applications/policy/filter/PhabricatorPolicyFilter.php b/src/applications/policy/filter/PhabricatorPolicyFilter.php
index 2ed1dec241..ef6108ceaa 100644
--- a/src/applications/policy/filter/PhabricatorPolicyFilter.php
+++ b/src/applications/policy/filter/PhabricatorPolicyFilter.php
@@ -1,319 +1,406 @@
<?php
final class PhabricatorPolicyFilter {
private $viewer;
private $objects;
private $capabilities;
private $raisePolicyExceptions;
private $userProjects;
+ private $customPolicies = array();
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();
+ $need_policies = 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 ($type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
+ $need_policies[$policy] = $policy;
+ }
}
}
+ if ($need_policies) {
+ $this->loadCustomPolicies(array_keys($need_policies));
+ }
+
// 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 not a public
// capability, restrict the policy to "user".
$capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) {
$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 if ($type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
+ if ($this->checkCustomPolicy($policy)) {
+ return true;
+ } else {
+ $this->rejectObject($object, $policy, $capability);
+ }
} else {
// Reject objects with unknown policies.
$this->rejectObject($object, false, $capability);
}
}
return false;
}
public function rejectObject(
PhabricatorPolicyInterface $object,
$policy,
$capability) {
if (!$this->raisePolicyExceptions) {
return;
}
$capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
$rejection = null;
if ($capobj) {
$rejection = $capobj->describeCapabilityRejection();
$capability_name = $capobj->getCapabilityName();
} else {
$capability_name = $capability;
}
if (!$rejection) {
// We couldn't find the capability object, or it doesn't provide a
// tailored rejection string.
$rejection = pht(
'You do not have the required capability ("%s") to do whatever you '.
'are trying to do.',
$capability);
}
$more = PhabricatorPolicy::getPolicyExplanation($this->viewer, $policy);
$exceptions = $object->describeAutomaticCapability($capability);
$details = array_filter(array_merge(array($more), (array)$exceptions));
// NOTE: Not every policy object has a PHID, just pull an arbitrary
// "unknown object" handle if this fails. We're just using this to provide
// a better error message if we can.
$phid = '?';
if (($object instanceof PhabricatorLiskDAO) ||
(method_exists($object, 'getPHID'))) {
try {
$phid = $object->getPHID();
} catch (Exception $ignored) {
// Ignore.
}
}
$handle = id(new PhabricatorHandleQuery())
->setViewer($this->viewer)
->withPHIDs(array($phid))
->executeOne();
$object_name = pht(
'%s %s',
$handle->getTypeName(),
$handle->getObjectName());
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
if ($is_serious) {
$title = pht(
'Access Denied: %s',
$object_name);
} else {
$title = pht(
'You Shall Not Pass: %s',
$object_name);
}
$full_message = pht(
'[%s] (%s) %s // %s',
$title,
$capability_name,
$rejection,
implode(' ', $details));
$exception = id(new PhabricatorPolicyException($full_message))
->setTitle($title)
->setRejection($rejection)
->setCapabilityName($capability_name)
->setMoreInfo($details);
throw $exception;
}
+
+ private function loadCustomPolicies(array $phids) {
+ $viewer = $this->viewer;
+ $viewer_phid = $viewer->getPHID();
+
+ $custom_policies = id(new PhabricatorPolicyQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($phids)
+ ->execute();
+ $custom_policies = mpull($custom_policies, null, 'getPHID');
+
+
+ $classes = array();
+ $values = array();
+ foreach ($custom_policies as $policy) {
+ foreach ($policy->getCustomRuleClasses() as $class) {
+ $classes[$class] = $class;
+ $values[$class][] = $policy->getCustomRuleValues($class);
+ }
+ }
+
+ foreach ($classes as $class => $ignored) {
+ $object = newv($class, array());
+ $object->willApplyRules($viewer, array_mergev($values[$class]));
+ $classes[$class] = $object;
+ }
+
+ foreach ($custom_policies as $policy) {
+ $policy->attachRuleObjects($classes);
+ }
+
+ if (empty($this->customPolicies[$viewer_phid])) {
+ $this->customPolicies[$viewer_phid] = array();
+ }
+
+ $this->customPolicies[$viewer->getPHID()] += $custom_policies;
+ }
+
+ private function checkCustomPolicy($policy_phid) {
+ $viewer = $this->viewer;
+ $viewer_phid = $viewer->getPHID();
+
+ $policy = $this->customPolicies[$viewer_phid][$policy_phid];
+
+ $objects = $policy->getRuleObjects();
+ $action = null;
+ foreach ($policy->getRules() as $rule) {
+ $object = idx($objects, idx($rule, 'rule'));
+ if (!$object) {
+ // Reject, this policy has a bogus rule.
+ return false;
+ }
+
+ // If the user matches this rule, use this action.
+ if ($object->applyRule($viewer, idx($rule, 'value'))) {
+ $action = idx($rule, 'action');
+ break;
+ }
+ }
+
+ if ($action === null) {
+ $action = $policy->getDefaultAction();
+ }
+
+ if ($action === PhabricatorPolicy::ACTION_ACCEPT) {
+ return true;
+ }
+
+ return false;
+ }
+
}
diff --git a/src/applications/policy/query/PhabricatorPolicyQuery.php b/src/applications/policy/query/PhabricatorPolicyQuery.php
index a58c085f54..f810c345f7 100644
--- a/src/applications/policy/query/PhabricatorPolicyQuery.php
+++ b/src/applications/policy/query/PhabricatorPolicyQuery.php
@@ -1,200 +1,219 @@
<?php
final class PhabricatorPolicyQuery extends PhabricatorQuery {
private $viewer;
private $object;
private $phids;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function setObject(PhabricatorPolicyInterface $object) {
$this->object = $object;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public static function loadPolicies(
PhabricatorUser $viewer,
PhabricatorPolicyInterface $object) {
$results = array();
- $policies = null;
- $global = self::getGlobalPolicies();
- $capabilities = $object->getCapabilities();
- foreach ($capabilities as $capability) {
- $policy = $object->getPolicy($capability);
- if (!$policy) {
- continue;
- }
+ $map = array();
+ foreach ($object->getCapabilities() as $capability) {
+ $map[$capability] = $object->getPolicy($capability);
+ }
- if (isset($global[$policy])) {
- $results[$capability] = $global[$policy];
- continue;
- }
+ $policies = id(new PhabricatorPolicyQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($map)
+ ->execute();
- if ($policies === null) {
- // This slightly overfetches data, but it shouldn't generally
- // be a problem.
- $policies = id(new PhabricatorPolicyQuery())
- ->setViewer($viewer)
- ->setObject($object)
- ->execute();
- }
-
- $results[$capability] = $policies[$policy];
+ foreach ($map as $capability => $phid) {
+ $results[$capability] = $policies[$phid];
}
return $results;
}
public static function renderPolicyDescriptions(
PhabricatorUser $viewer,
PhabricatorPolicyInterface $object,
$icon = false) {
$policies = self::loadPolicies($viewer, $object);
foreach ($policies as $capability => $policy) {
$policies[$capability] = $policy->renderDescription($icon);
}
return $policies;
}
public function execute() {
if (!$this->viewer) {
throw new Exception('Call setViewer() before execute()!');
}
- $results = $this->getGlobalPolicies();
-
- if ($this->viewer->getPHID()) {
- $projects = id(new PhabricatorProjectQuery())
- ->setViewer($this->viewer)
- ->withMemberPHIDs(array($this->viewer->getPHID()))
- ->execute();
- if ($projects) {
- foreach ($projects as $project) {
- $results[] = id(new PhabricatorPolicy())
- ->setType(PhabricatorPolicyType::TYPE_PROJECT)
- ->setPHID($project->getPHID())
- ->setHref('/project/view/'.$project->getID().'/')
- ->setName($project->getName());
- }
- }
+ if ($this->object && $this->phids) {
+ throw new Exception(
+ "You can not issue a policy query with both setObject() and ".
+ "setPHIDs().");
+ } else if ($this->object) {
+ $phids = $this->loadObjectPolicyPHIDs();
+ } else {
+ $phids = $this->phids;
}
- $results = mpull($results, null, 'getPHID');
+ $phids = array_fuse($phids);
- $other_policies = array();
- if ($this->object) {
- $capabilities = $this->object->getCapabilities();
- foreach ($capabilities as $capability) {
- $policy = $this->object->getPolicy($capability);
- if (!$policy) {
- continue;
- }
- $other_policies[$policy] = $policy;
+ $results = array();
+
+ // First, load global policies.
+ foreach ($this->getGlobalPolicies() as $phid => $policy) {
+ if (isset($phids[$phid])) {
+ $results[$phid] = $policy;
+ unset($phids[$phid]);
}
}
- // If this install doesn't have "Public" enabled, remove it as an option
- // unless the object already has a "Public" policy. In this case we retain
- // the policy but enforce it as thought it was "All Users".
- $show_public = PhabricatorEnv::getEnvConfig('policy.allow-public');
- if (!$show_public &&
- empty($other_policies[PhabricatorPolicies::POLICY_PUBLIC])) {
- unset($results[PhabricatorPolicies::POLICY_PUBLIC]);
- }
+ // If we still need policies, we're going to have to fetch data. Bucket
+ // the remaining policies into rule-based policies and handle-based
+ // policies.
+ if ($phids) {
+ $rule_policies = array();
+ $handle_policies = array();
+ foreach ($phids as $phid) {
+ $phid_type = phid_get_type($phid);
+ if ($phid_type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
+ $rule_policies[$phid] = $phid;
+ } else {
+ $handle_policies[$phid] = $phid;
+ }
+ }
- $other_policies = array_diff_key($other_policies, $results);
+ if ($handle_policies) {
+ $handles = id(new PhabricatorHandleQuery())
+ ->setViewer($this->viewer)
+ ->withPHIDs($handle_policies)
+ ->execute();
+ foreach ($handle_policies as $phid) {
+ $results[$phid] = PhabricatorPolicy::newFromPolicyAndHandle(
+ $phid,
+ $handles[$phid]);
+ }
+ }
- if ($other_policies) {
- $handles = id(new PhabricatorHandleQuery())
- ->setViewer($this->viewer)
- ->withPHIDs($other_policies)
- ->execute();
- foreach ($other_policies as $phid) {
- $results[$phid] = PhabricatorPolicy::newFromPolicyAndHandle(
- $phid,
- $handles[$phid]);
+ if ($rule_policies) {
+ $rules = id(new PhabricatorPolicy())->loadAllWhere(
+ 'phid IN (%Ls)',
+ $rule_policies);
+ $results += mpull($rules, null, 'getPHID');
}
}
$results = msort($results, 'getSortKey');
- if ($this->phids) {
- $phids = array_fuse($this->phids);
- foreach ($results as $key => $result) {
- if (empty($phids[$result->getPHID()])) {
- unset($results[$key]);
- }
- }
- }
-
return $results;
}
public static function isGlobalPolicy($policy) {
$globalPolicies = self::getGlobalPolicies();
if (isset($globalPolicies[$policy])) {
return true;
}
return false;
}
public static function getGlobalPolicy($policy) {
if (!self::isGlobalPolicy($policy)) {
throw new Exception("Policy '{$policy}' is not a global policy!");
}
return idx(self::getGlobalPolicies(), $policy);
}
private static function getGlobalPolicies() {
static $constants = array(
PhabricatorPolicies::POLICY_PUBLIC,
PhabricatorPolicies::POLICY_USER,
PhabricatorPolicies::POLICY_ADMIN,
PhabricatorPolicies::POLICY_NOONE,
);
$results = array();
foreach ($constants as $constant) {
$results[$constant] = id(new PhabricatorPolicy())
->setType(PhabricatorPolicyType::TYPE_GLOBAL)
->setPHID($constant)
->setName(self::getGlobalPolicyName($constant))
->makeEphemeral();
}
return $results;
}
private static function getGlobalPolicyName($policy) {
switch ($policy) {
case PhabricatorPolicies::POLICY_PUBLIC:
return pht('Public (No Login Required)');
case PhabricatorPolicies::POLICY_USER:
return pht('All Users');
case PhabricatorPolicies::POLICY_ADMIN:
return pht('Administrators');
case PhabricatorPolicies::POLICY_NOONE:
return pht('No One');
default:
return pht('Unknown Policy');
}
}
+ private function loadObjectPolicyPHIDs() {
+ $phids = array();
+
+ if ($this->viewer->getPHID()) {
+ $projects = id(new PhabricatorProjectQuery())
+ ->setViewer($this->viewer)
+ ->withMemberPHIDs(array($this->viewer->getPHID()))
+ ->execute();
+ foreach ($projects as $project) {
+ $phids[] = $project->getPHID();
+ }
+ }
+
+ $capabilities = $this->object->getCapabilities();
+ foreach ($capabilities as $capability) {
+ $policy = $this->object->getPolicy($capability);
+ if (!$policy) {
+ continue;
+ }
+ $phids[] = $policy;
+ }
+
+ // If this install doesn't have "Public" enabled, don't include it as an
+ // option unless the object already has a "Public" policy. In this case we
+ // retain the policy but enforce it as though it was "All Users".
+ $show_public = PhabricatorEnv::getEnvConfig('policy.allow-public');
+ foreach ($this->getGlobalPolicies() as $phid => $policy) {
+ if ($phid == PhabricatorPolicies::POLICY_PUBLIC) {
+ if (!$show_public) {
+ continue;
+ }
+ }
+ $phids[] = $phid;
+ }
+
+ return $phids;
+ }
+
}
diff --git a/src/applications/policy/rule/PhabricatorPolicyRuleUsers.php b/src/applications/policy/rule/PhabricatorPolicyRuleUsers.php
index a4f28aa5dc..7ac490ed28 100644
--- a/src/applications/policy/rule/PhabricatorPolicyRuleUsers.php
+++ b/src/applications/policy/rule/PhabricatorPolicyRuleUsers.php
@@ -1,44 +1,49 @@
<?php
final class PhabricatorPolicyRuleUsers
extends PhabricatorPolicyRule {
public function getRuleDescription() {
return pht('users');
}
public function applyRule(PhabricatorUser $viewer, $value) {
- return isset($value[$viewer->getPHID()]);
+ foreach ($value as $phid) {
+ if ($phid == $viewer->getPHID()) {
+ return true;
+ }
+ }
+ return false;
}
public function getValueControlType() {
return self::CONTROL_TYPE_TOKENIZER;
}
public function getValueControlTemplate() {
return array(
'markup' => new AphrontTokenizerTemplateView(),
'uri' => '/typeahead/common/accounts/',
'placeholder' => pht('Type a user name...'),
);
}
public function getRuleOrder() {
return 100;
}
public function getValueForStorage($value) {
PhutilTypeSpec::newFromString('list<string>')->check($value);
return array_values($value);
}
public function getValueForDisplay(PhabricatorUser $viewer, $value) {
$handles = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs($value)
->execute();
return mpull($handles, 'getFullName', 'getPHID');
}
}
diff --git a/src/applications/policy/storage/PhabricatorPolicy.php b/src/applications/policy/storage/PhabricatorPolicy.php
index b1f60f1268..e14f49c6f9 100644
--- a/src/applications/policy/storage/PhabricatorPolicy.php
+++ b/src/applications/policy/storage/PhabricatorPolicy.php
@@ -1,231 +1,284 @@
<?php
final class PhabricatorPolicy
extends PhabricatorPolicyDAO {
const ACTION_ACCEPT = 'accept';
const ACTION_DENY = 'deny';
private $name;
private $type;
private $href;
private $icon;
protected $rules = array();
protected $defaultAction = self::ACTION_DENY;
+ private $ruleObjects = self::ATTACHABLE;
+
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'rules' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPolicyPHIDTypePolicy::TYPECONST);
}
public static function newFromPolicyAndHandle(
$policy_identifier,
PhabricatorObjectHandle $handle = null) {
$is_global = PhabricatorPolicyQuery::isGlobalPolicy($policy_identifier);
if ($is_global) {
return PhabricatorPolicyQuery::getGlobalPolicy($policy_identifier);
}
if (!$handle) {
throw new Exception(
"Policy identifier is an object PHID ('{$policy_identifier}'), but no ".
"object handle was provided. A handle must be provided for object ".
"policies.");
}
$handle_phid = $handle->getPHID();
if ($policy_identifier != $handle_phid) {
throw new Exception(
"Policy identifier is an object PHID ('{$policy_identifier}'), but ".
"the provided handle has a different PHID ('{$handle_phid}'). The ".
"handle must correspond to the policy identifier.");
}
$policy = id(new PhabricatorPolicy())
->setPHID($policy_identifier)
->setHref($handle->getURI());
$phid_type = phid_get_type($policy_identifier);
switch ($phid_type) {
case PhabricatorProjectPHIDTypeProject::TYPECONST:
$policy->setType(PhabricatorPolicyType::TYPE_PROJECT);
$policy->setName($handle->getName());
break;
default:
$policy->setType(PhabricatorPolicyType::TYPE_MASKED);
$policy->setName($handle->getFullName());
break;
}
$policy->makeEphemeral();
return $policy;
}
public function setType($type) {
$this->type = $type;
return $this;
}
public function getType() {
return $this->type;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setHref($href) {
$this->href = $href;
return $this;
}
public function getHref() {
return $this->href;
}
public function getIcon() {
switch ($this->getType()) {
case PhabricatorPolicyType::TYPE_GLOBAL:
static $map = array(
PhabricatorPolicies::POLICY_PUBLIC => 'policy-public',
PhabricatorPolicies::POLICY_USER => 'policy-all',
PhabricatorPolicies::POLICY_ADMIN => 'policy-admin',
PhabricatorPolicies::POLICY_NOONE => 'policy-noone',
);
return idx($map, $this->getPHID(), 'policy-unknown');
break;
case PhabricatorPolicyType::TYPE_PROJECT:
return 'policy-project';
break;
case PhabricatorPolicyType::TYPE_MASKED:
return 'policy-custom';
break;
default:
return 'policy-unknown';
break;
}
}
public function getSortKey() {
return sprintf(
'%02d%s',
PhabricatorPolicyType::getPolicyTypeOrder($this->getType()),
$this->getSortName());
}
private function getSortName() {
if ($this->getType() == PhabricatorPolicyType::TYPE_GLOBAL) {
static $map = array(
PhabricatorPolicies::POLICY_PUBLIC => 0,
PhabricatorPolicies::POLICY_USER => 1,
PhabricatorPolicies::POLICY_ADMIN => 2,
PhabricatorPolicies::POLICY_NOONE => 3,
);
return idx($map, $this->getPHID());
}
return $this->getName();
}
public static function getPolicyExplanation(
PhabricatorUser $viewer,
$policy) {
switch ($policy) {
case PhabricatorPolicies::POLICY_PUBLIC:
return pht('This object is public.');
case PhabricatorPolicies::POLICY_USER:
return pht('Logged in users can take this action.');
case PhabricatorPolicies::POLICY_ADMIN:
return pht('Administrators can take this action.');
case PhabricatorPolicies::POLICY_NOONE:
return pht('By default, no one can take this action.');
default:
$handle = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs(array($policy))
->executeOne();
$type = phid_get_type($policy);
if ($type == PhabricatorProjectPHIDTypeProject::TYPECONST) {
return pht(
'Members of the project "%s" can take this action.',
$handle->getFullName());
} else if ($type == PhabricatorPeoplePHIDTypeUser::TYPECONST) {
return pht(
'%s can take this action.',
$handle->getFullName());
} else {
return pht(
'This object has an unknown or invalid policy setting ("%s").',
$policy);
}
}
}
public function getFullName() {
switch ($this->getType()) {
case PhabricatorPolicyType::TYPE_PROJECT:
return pht('Project: %s', $this->getName());
case PhabricatorPolicyType::TYPE_MASKED:
return pht('Other: %s', $this->getName());
default:
return $this->getName();
}
}
public function renderDescription($icon=false) {
$img = null;
if ($icon) {
$img = id(new PHUIIconView())
->setSpriteSheet(PHUIIconView::SPRITE_STATUS)
->setSpriteIcon($this->getIcon());
}
if ($this->getHref()) {
$desc = phutil_tag(
'a',
array(
'href' => $this->getHref(),
'class' => 'policy-link',
),
array(
$img,
$this->getName(),
));
} else {
if ($img) {
$desc = array($img, $this->getName());
} else {
$desc = $this->getName();
}
}
switch ($this->getType()) {
case PhabricatorPolicyType::TYPE_PROJECT:
return pht('%s (Project)', $desc);
case PhabricatorPolicyType::TYPE_MASKED:
return pht(
'%s (You do not have permission to view policy details.)',
$desc);
default:
return $desc;
}
}
+
+ /**
+ * Return a list of custom rule classes (concrete subclasses of
+ * @{class:PhabricatorPolicyRule}) this policy uses.
+ *
+ * @return list<string> List of class names.
+ */
+ public function getCustomRuleClasses() {
+ $classes = array();
+
+ foreach ($this->getRules() as $rule) {
+ $class = idx($rule, 'rule');
+ try {
+ if (class_exists($class)) {
+ $classes[$class] = $class;
+ }
+ } catch (Exception $ex) {
+ continue;
+ }
+ }
+
+ return array_keys($classes);
+ }
+
+ /**
+ * Return a list of all values used by a given rule class to implement this
+ * policy. This is used to bulk load data (like project memberships) in order
+ * to apply policy filters efficiently.
+ *
+ * @param string Policy rule classname.
+ * @return list<wild> List of values used in this policy.
+ */
+ public function getCustomRuleValues($rule_class) {
+ $values = array();
+ foreach ($this->getRules() as $rule) {
+ if ($rule['rule'] == $rule_class) {
+ $values[] = $rule['value'];
+ }
+ }
+ return $values;
+ }
+
+ public function attachRuleObjects(array $objects) {
+ $this->ruleObjects = $objects;
+ return $this;
+ }
+
+ public function getRuleObjects() {
+ return $this->assertAttached($this->ruleObjects);
+ }
+
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Nov 6, 9:06 AM (6 h, 18 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
321759
Default Alt Text
(42 KB)

Event Timeline