Page MenuHomePhorge

No OneTemporary

diff --git a/src/applications/differential/capability/DifferentialCapabilityDefaultView.php b/src/applications/differential/capability/DifferentialCapabilityDefaultView.php
index c33fdc7a03..a0dbd99e5f 100644
--- a/src/applications/differential/capability/DifferentialCapabilityDefaultView.php
+++ b/src/applications/differential/capability/DifferentialCapabilityDefaultView.php
@@ -1,16 +1,20 @@
<?php
final class DifferentialCapabilityDefaultView
extends PhabricatorPolicyCapability {
const CAPABILITY = 'differential.default.view';
public function getCapabilityKey() {
return self::CAPABILITY;
}
public function getCapabilityName() {
return pht('Default View Policy');
}
+ public function shouldAllowPublicPolicySetting() {
+ return true;
+ }
+
}
diff --git a/src/applications/maniphest/capability/ManiphestCapabilityDefaultView.php b/src/applications/maniphest/capability/ManiphestCapabilityDefaultView.php
index 73638007a1..cd181561dd 100644
--- a/src/applications/maniphest/capability/ManiphestCapabilityDefaultView.php
+++ b/src/applications/maniphest/capability/ManiphestCapabilityDefaultView.php
@@ -1,16 +1,20 @@
<?php
final class ManiphestCapabilityDefaultView
extends PhabricatorPolicyCapability {
const CAPABILITY = 'maniphest.default.view';
public function getCapabilityKey() {
return self::CAPABILITY;
}
public function getCapabilityName() {
return pht('Default View Policy');
}
+ public function shouldAllowPublicPolicySetting() {
+ return true;
+ }
+
}
diff --git a/src/applications/meta/controller/PhabricatorApplicationEditController.php b/src/applications/meta/controller/PhabricatorApplicationEditController.php
index d401f4f8c5..635d6d299b 100644
--- a/src/applications/meta/controller/PhabricatorApplicationEditController.php
+++ b/src/applications/meta/controller/PhabricatorApplicationEditController.php
@@ -1,164 +1,164 @@
<?php
final class PhabricatorApplicationEditController
extends PhabricatorApplicationsController{
private $application;
public function willProcessRequest(array $data) {
$this->application = $data['application'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$application = id(new PhabricatorApplicationQuery())
->setViewer($user)
->withClasses(array($this->application))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$application) {
return new Aphront404Response();
}
$title = $application->getName();
$view_uri = $this->getApplicationURI('view/'.get_class($application).'/');
$policies = id(new PhabricatorPolicyQuery())
->setViewer($user)
->setObject($application)
->execute();
if ($request->isFormPost()) {
$result = array();
foreach ($application->getCapabilities() as $capability) {
$old = $application->getPolicy($capability);
$new = $request->getStr('policy:'.$capability);
if ($old == $new) {
// No change to the setting.
continue;
}
if (empty($policies[$new])) {
// Can't set the policy to something invalid.
continue;
}
- if ($new == PhabricatorPolicies::POLICY_PUBLIC &&
- $capability != PhabricatorPolicyCapability::CAN_VIEW) {
- // Can't set policies other than "view" to public.
+ $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
+ if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) {
+ // Can't set non-public policies to public.
continue;
}
$result[$capability] = $new;
}
if ($result) {
$key = 'phabricator.application-settings';
$config_entry = PhabricatorConfigEntry::loadConfigEntry($key);
$value = $config_entry->getValue();
$phid = $application->getPHID();
if (empty($value[$phid])) {
$value[$application->getPHID()] = array();
}
if (empty($value[$phid]['policy'])) {
$value[$phid]['policy'] = array();
}
$value[$phid]['policy'] = $result + $value[$phid]['policy'];
// Don't allow users to make policy edits which would lock them out of
// applications, since they would be unable to undo those actions.
PhabricatorEnv::overrideConfig($key, $value);
PhabricatorPolicyFilter::mustRetainCapability(
$user,
$application,
PhabricatorPolicyCapability::CAN_VIEW);
PhabricatorPolicyFilter::mustRetainCapability(
$user,
$application,
PhabricatorPolicyCapability::CAN_EDIT);
PhabricatorConfigEditor::storeNewValue(
$config_entry,
$value,
$this->getRequest());
}
return id(new AphrontRedirectResponse())->setURI($view_uri);
}
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$user,
$application);
$form = id(new AphrontFormView())
->setUser($user);
foreach ($application->getCapabilities() as $capability) {
$label = $application->getCapabilityLabel($capability);
$can_edit = $application->isCapabilityEditable($capability);
$caption = $application->getCapabilityCaption($capability);
if (!$can_edit) {
$form->appendChild(
id(new AphrontFormStaticControl())
->setLabel($label)
->setValue(idx($descriptions, $capability))
->setCaption($caption));
} else {
$form->appendChild(
id(new AphrontFormPolicyControl())
->setUser($user)
->setCapability($capability)
->setPolicyObject($application)
->setPolicies($policies)
->setLabel($label)
->setName('policy:'.$capability)
->setCaption($caption));
}
}
$form->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save Policies'))
->addCancelButton($view_uri));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($application->getName())
->setHref($view_uri));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit Policies')));
$header = id(new PHUIHeaderView())
->setHeader(pht('Edit Policies: %s', $application->getName()));
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->setForm($form);
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
),
array(
'title' => $title,
'device' => true,
));
}
}
diff --git a/src/applications/policy/capability/PhabricatorPolicyCapability.php b/src/applications/policy/capability/PhabricatorPolicyCapability.php
index ea925f2541..a34e0f76bd 100644
--- a/src/applications/policy/capability/PhabricatorPolicyCapability.php
+++ b/src/applications/policy/capability/PhabricatorPolicyCapability.php
@@ -1,63 +1,72 @@
<?php
abstract class PhabricatorPolicyCapability extends Phobject {
const CAN_VIEW = 'view';
const CAN_EDIT = 'edit';
const CAN_JOIN = 'join';
/**
* Get the unique key identifying this capability. This key must be globally
* unique. Application capabilities should be namespaced. For example:
*
* application.create
*
* @return string Globally unique capability key.
*/
abstract public function getCapabilityKey();
/**
* Return a human-readable descriptive name for this capability, like
* "Can View".
*
* @return string Human-readable name describing the capability.
*/
abstract public function getCapabilityName();
/**
* Return a human-readable string describing what not having this capability
* prevents the user from doing. For example:
*
* - You do not have permission to edit this object.
* - You do not have permission to create new tasks.
*
* @return string Human-readable name describing what failing a check for this
* capability prevents the user from doing.
*/
public function describeCapabilityRejection() {
return null;
}
+ /**
+ * Can this capability be set to "public"? Broadly, this is only appropriate
+ * for view and view-related policies.
+ *
+ * @return bool True to allow the "public" policy. Returns false by default.
+ */
+ public function shouldAllowPublicPolicySetting() {
+ return false;
+ }
final public static function getCapabilityByKey($key) {
return idx(self::getCapabilityMap(), $key);
}
final public static function getCapabilityMap() {
static $map;
if ($map === null) {
$capabilities = id(new PhutilSymbolLoader())
->setAncestorClass(__CLASS__)
->loadObjects();
$map = mpull($capabilities, null, 'getCapabilityKey');
}
return $map;
}
}
diff --git a/src/applications/policy/capability/PhabricatorPolicyCapabilityCanView.php b/src/applications/policy/capability/PhabricatorPolicyCapabilityCanView.php
index b9d2df47b9..f56b1d7407 100644
--- a/src/applications/policy/capability/PhabricatorPolicyCapabilityCanView.php
+++ b/src/applications/policy/capability/PhabricatorPolicyCapabilityCanView.php
@@ -1,18 +1,22 @@
<?php
final class PhabricatorPolicyCapabilityCanView
extends PhabricatorPolicyCapability {
public function getCapabilityKey() {
return self::CAN_VIEW;
}
public function getCapabilityName() {
return pht('Can View');
}
public function describeCapabilityRejection() {
return pht('You do not have permission to view this object.');
}
+ public function shouldAllowPublicPolicySetting() {
+ return true;
+ }
+
}
diff --git a/src/applications/policy/filter/PhabricatorPolicyFilter.php b/src/applications/policy/filter/PhabricatorPolicyFilter.php
index 8bc32e22a7..2ed1dec241 100644
--- a/src/applications/policy/filter/PhabricatorPolicyFilter.php
+++ b/src/applications/policy/filter/PhabricatorPolicyFilter.php
@@ -1,318 +1,319 @@
<?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) {
+ // 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 {
// 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;
}
}
diff --git a/src/view/form/control/AphrontFormPolicyControl.php b/src/view/form/control/AphrontFormPolicyControl.php
index 3db5be8d18..c145a9707b 100644
--- a/src/view/form/control/AphrontFormPolicyControl.php
+++ b/src/view/form/control/AphrontFormPolicyControl.php
@@ -1,79 +1,83 @@
<?php
final class AphrontFormPolicyControl extends AphrontFormControl {
private $object;
private $capability;
private $policies;
public function setPolicyObject(PhabricatorPolicyInterface $object) {
$this->object = $object;
return $this;
}
public function setPolicies(array $policies) {
assert_instances_of($policies, 'PhabricatorPolicy');
$this->policies = $policies;
return $this;
}
public function setCapability($capability) {
$this->capability = $capability;
$labels = array(
PhabricatorPolicyCapability::CAN_VIEW => pht('Visible To'),
PhabricatorPolicyCapability::CAN_EDIT => pht('Editable By'),
PhabricatorPolicyCapability::CAN_JOIN => pht('Joinable By'),
);
$this->setLabel(idx($labels, $this->capability, pht('Unknown Policy')));
return $this;
}
protected function getCustomControlClass() {
return 'aphront-form-control-policy';
}
protected function getOptions() {
+ $capability = $this->capability;
+
$options = array();
foreach ($this->policies as $policy) {
- if (($policy->getPHID() == PhabricatorPolicies::POLICY_PUBLIC) &&
- ($this->capability != PhabricatorPolicyCapability::CAN_VIEW)) {
- // Never expose "Public" for anything except "Can View".
- continue;
+ if ($policy->getPHID() == PhabricatorPolicies::POLICY_PUBLIC) {
+ // Never expose "Public" for capabilities which don't support it.
+ $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
+ if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) {
+ continue;
+ }
}
$type_name = PhabricatorPolicyType::getPolicyTypeName($policy->getType());
$options[$type_name][$policy->getPHID()] = $policy->getFullName();
}
return $options;
}
protected function renderInput() {
if (!$this->object) {
throw new Exception(pht("Call setPolicyObject() before rendering!"));
}
if (!$this->capability) {
throw new Exception(pht("Call setCapability() before rendering!"));
}
$policy = $this->object->getPolicy($this->capability);
if (!$policy) {
// TODO: Make this configurable.
$policy = PhabricatorPolicies::POLICY_USER;
}
$this->setValue($policy);
return AphrontFormSelectControl::renderSelectTag(
$this->getValue(),
$this->getOptions(),
array(
'name' => $this->getName(),
'disabled' => $this->getDisabled() ? 'disabled' : null,
'id' => $this->getID(),
));
}
}

File Metadata

Mime Type
text/x-diff
Expires
Jan 19 2025, 22:55 (6 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1129621
Default Alt Text
(22 KB)

Event Timeline