Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2896338
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
22 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment