Page MenuHomePhabricator

D13257.diff
No OneTemporary

D13257.diff

diff --git a/src/applications/maniphest/policyrule/ManiphestTaskAuthorPolicyRule.php b/src/applications/maniphest/policyrule/ManiphestTaskAuthorPolicyRule.php
--- a/src/applications/maniphest/policyrule/ManiphestTaskAuthorPolicyRule.php
+++ b/src/applications/maniphest/policyrule/ManiphestTaskAuthorPolicyRule.php
@@ -3,6 +3,18 @@
final class ManiphestTaskAuthorPolicyRule
extends PhabricatorPolicyRule {
+ public function getObjectPolicyKey() {
+ return 'maniphest.author';
+ }
+
+ public function getObjectPolicyName() {
+ return pht('Task Author');
+ }
+
+ public function getPolicyExplanation() {
+ return pht('The author of this task can take this action.');
+ }
+
public function getRuleDescription() {
return pht('task author');
}
diff --git a/src/applications/meta/controller/PhabricatorApplicationEditController.php b/src/applications/meta/controller/PhabricatorApplicationEditController.php
--- a/src/applications/meta/controller/PhabricatorApplicationEditController.php
+++ b/src/applications/meta/controller/PhabricatorApplicationEditController.php
@@ -136,6 +136,20 @@
$template = $application->getCapabilityTemplatePHIDType($capability);
if ($template) {
+ $phid_types = PhabricatorPHIDType::getAllTypes();
+ $phid_type = idx($phid_types, $template);
+ if ($phid_type) {
+ $template_object = $phid_type->newObject();
+ if ($template_object) {
+ $template_policies = id(new PhabricatorPolicyQuery())
+ ->setViewer($user)
+ ->setObject($template_object)
+ ->execute();
+ $control->setPolicies($template_policies);
+ $control->setTemplateObject($template_object);
+ }
+ }
+
$control->setTemplatePHIDType($template);
}
diff --git a/src/applications/policy/constants/PhabricatorPolicyType.php b/src/applications/policy/constants/PhabricatorPolicyType.php
--- a/src/applications/policy/constants/PhabricatorPolicyType.php
+++ b/src/applications/policy/constants/PhabricatorPolicyType.php
@@ -3,6 +3,7 @@
final class PhabricatorPolicyType extends PhabricatorPolicyConstants {
const TYPE_GLOBAL = 'global';
+ const TYPE_OBJECT = 'object';
const TYPE_USER = 'user';
const TYPE_CUSTOM = 'custom';
const TYPE_PROJECT = 'project';
@@ -11,9 +12,10 @@
public static function getPolicyTypeOrder($type) {
static $map = array(
self::TYPE_GLOBAL => 0,
- self::TYPE_USER => 1,
- self::TYPE_CUSTOM => 2,
- self::TYPE_PROJECT => 3,
+ self::TYPE_OBJECT => 1,
+ self::TYPE_USER => 2,
+ self::TYPE_CUSTOM => 3,
+ self::TYPE_PROJECT => 4,
self::TYPE_MASKED => 9,
);
return idx($map, $type, 9);
@@ -23,6 +25,8 @@
switch ($type) {
case self::TYPE_GLOBAL:
return pht('Basic Policies');
+ case self::TYPE_OBJECT:
+ return pht('Object Policies');
case self::TYPE_USER:
return pht('User Policies');
case self::TYPE_CUSTOM:
diff --git a/src/applications/policy/filter/PhabricatorPolicyFilter.php b/src/applications/policy/filter/PhabricatorPolicyFilter.php
--- a/src/applications/policy/filter/PhabricatorPolicyFilter.php
+++ b/src/applications/policy/filter/PhabricatorPolicyFilter.php
@@ -8,6 +8,7 @@
private $raisePolicyExceptions;
private $userProjects;
private $customPolicies = array();
+ private $objectPolicies = array();
private $forcedPolicy;
public static function mustRetainCapability(
@@ -131,6 +132,7 @@
$need_projects = array();
$need_policies = array();
+ $need_objpolicies = array();
foreach ($objects as $key => $object) {
$object_capabilities = $object->getCapabilities();
foreach ($capabilities as $capability) {
@@ -143,17 +145,29 @@
}
$policy = $this->getObjectPolicy($object, $capability);
+
+ if (PhabricatorPolicyQuery::isObjectPolicy($policy)) {
+ $need_objpolicies[$policy][] = $object;
+ continue;
+ }
+
$type = phid_get_type($policy);
if ($type == PhabricatorProjectProjectPHIDType::TYPECONST) {
$need_projects[$policy] = $policy;
+ continue;
}
if ($type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
$need_policies[$policy][] = $object;
+ continue;
}
}
}
+ if ($need_objpolicies) {
+ $this->loadObjectPolicies($need_objpolicies);
+ }
+
if ($need_policies) {
$this->loadCustomPolicies($need_policies);
}
@@ -486,6 +500,15 @@
$this->rejectObject($object, $policy, $capability);
break;
default:
+ if (PhabricatorPolicyQuery::isObjectPolicy($policy)) {
+ if ($this->checkObjectPolicy($policy, $object)) {
+ return true;
+ } else {
+ $this->rejectObject($object, $policy, $capability);
+ break;
+ }
+ }
+
$type = phid_get_type($policy);
if ($type == PhabricatorProjectProjectPHIDType::TYPECONST) {
if (!empty($this->userProjects[$viewer->getPHID()][$policy])) {
@@ -573,6 +596,32 @@
throw $exception;
}
+ private function loadObjectPolicies(array $map) {
+ $viewer = $this->viewer;
+ $viewer_phid = $viewer->getPHID();
+
+ $rules = PhabricatorPolicyQuery::getObjectPolicyRules(null);
+
+ $results = array();
+ foreach ($map as $key => $object_list) {
+ $rule = idx($rules, $key);
+ if (!$rule) {
+ continue;
+ }
+
+ foreach ($object_list as $object_key => $object) {
+ if (!$rule->canApplyToObject($object)) {
+ unset($object_list[$object_key]);
+ }
+ }
+
+ $rule->willApplyRules($viewer, array(), $object_list);
+ $results[$key] = $rule;
+ }
+
+ $this->objectPolicies[$viewer_phid] = $results;
+ }
+
private function loadCustomPolicies(array $map) {
$viewer = $this->viewer;
$viewer_phid = $viewer->getPHID();
@@ -627,6 +676,24 @@
$this->customPolicies[$viewer->getPHID()] += $custom_policies;
}
+ private function checkObjectPolicy(
+ $policy_phid,
+ PhabricatorPolicyInterface $object) {
+ $viewer = $this->viewer;
+ $viewer_phid = $viewer->getPHID();
+
+ $rule = idx($this->objectPolicies[$viewer_phid], $policy_phid);
+ if (!$rule) {
+ return false;
+ }
+
+ if (!$rule->canApplyToObject($object)) {
+ return false;
+ }
+
+ return $rule->applyRule($viewer, null, $object);
+ }
+
private function checkCustomPolicy(
$policy_phid,
PhabricatorPolicyInterface $object) {
diff --git a/src/applications/policy/query/PhabricatorPolicyQuery.php b/src/applications/policy/query/PhabricatorPolicyQuery.php
--- a/src/applications/policy/query/PhabricatorPolicyQuery.php
+++ b/src/applications/policy/query/PhabricatorPolicyQuery.php
@@ -6,6 +6,8 @@
private $object;
private $phids;
+ const OBJECT_POLICY_PREFIX = 'obj.';
+
public function setObject(PhabricatorPolicyInterface $object) {
$this->object = $object;
return $this;
@@ -71,7 +73,15 @@
$results = array();
// First, load global policies.
- foreach ($this->getGlobalPolicies() as $phid => $policy) {
+ foreach (self::getGlobalPolicies() as $phid => $policy) {
+ if (isset($phids[$phid])) {
+ $results[$phid] = $policy;
+ unset($phids[$phid]);
+ }
+ }
+
+ // Now, load object policies.
+ foreach (self::getObjectPolicies($this->object) as $phid => $policy) {
if (isset($phids[$phid])) {
$results[$phid] = $policy;
unset($phids[$phid]);
@@ -212,7 +222,7 @@
// 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) {
+ foreach (self::getGlobalPolicies() as $phid => $policy) {
if ($phid == PhabricatorPolicies::POLICY_PUBLIC) {
if (!$show_public) {
continue;
@@ -221,6 +231,10 @@
$phids[] = $phid;
}
+ foreach (self::getObjectPolicies($this->object) as $phid => $policy) {
+ $phids[] = $phid;
+ }
+
return $phids;
}
@@ -234,4 +248,99 @@
return 'PhabricatorPolicyApplication';
}
+ public static function isSpecialPolicy($identifier) {
+ if (self::isObjectPolicy($identifier)) {
+ return true;
+ }
+
+ if (self::isGlobalPolicy($identifier)) {
+ return true;
+ }
+
+ return false;
+ }
+
+
+/* -( Object Policies )---------------------------------------------------- */
+
+
+ public static function isObjectPolicy($identifier) {
+ $prefix = self::OBJECT_POLICY_PREFIX;
+ return !strncmp($identifier, $prefix, strlen($prefix));
+ }
+
+ public static function getObjectPolicy($identifier) {
+ if (!self::isObjectPolicy($identifier)) {
+ return null;
+ }
+
+ $policies = self::getObjectPolicies(null);
+ return idx($policies, $identifier);
+ }
+
+ public static function getObjectPolicyRule($identifier) {
+ if (!self::isObjectPolicy($identifier)) {
+ return null;
+ }
+
+ $rules = self::getObjectPolicyRules(null);
+ return idx($rules, $identifier);
+ }
+
+ public static function getObjectPolicies($object) {
+ $rule_map = self::getObjectPolicyRules($object);
+
+ $results = array();
+ foreach ($rule_map as $key => $rule) {
+ $results[$key] = id(new PhabricatorPolicy())
+ ->setType(PhabricatorPolicyType::TYPE_OBJECT)
+ ->setPHID($key)
+ ->setIcon($rule->getObjectPolicyIcon())
+ ->setName($rule->getObjectPolicyName())
+ ->setShortName($rule->getObjectPolicyShortName())
+ ->makeEphemeral();
+ }
+
+ return $results;
+ }
+
+ public static function getObjectPolicyRules($object) {
+ $rules = id(new PhutilSymbolLoader())
+ ->setAncestorClass('PhabricatorPolicyRule')
+ ->loadObjects();
+
+ $results = array();
+ foreach ($rules as $rule) {
+ $key = $rule->getObjectPolicyKey();
+ if (!$key) {
+ continue;
+ }
+
+ $full_key = self::OBJECT_POLICY_PREFIX.$key;
+ if (isset($results[$full_key])) {
+ throw new Exception(
+ pht(
+ 'Two policy rules (of classes "%s" and "%s") define the same '.
+ 'object policy key ("%s"), but each object policy rule must use '.
+ 'a unique key.',
+ get_class($rule),
+ get_class($results[$full_key]),
+ $key));
+ }
+
+ $results[$full_key] = $rule;
+ }
+
+ if ($object !== null) {
+ foreach ($results as $key => $rule) {
+ if (!$rule->canApplyToObject($object)) {
+ unset($results[$key]);
+ }
+ }
+ }
+
+ return $results;
+ }
+
+
}
diff --git a/src/applications/policy/rule/PhabricatorPolicyRule.php b/src/applications/policy/rule/PhabricatorPolicyRule.php
--- a/src/applications/policy/rule/PhabricatorPolicyRule.php
+++ b/src/applications/policy/rule/PhabricatorPolicyRule.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @task objectpolicy Implementing Object Policies
+ */
abstract class PhabricatorPolicyRule {
const CONTROL_TYPE_TEXT = 'text';
@@ -93,4 +96,38 @@
return true;
}
+
+/* -( Implementing Object Policies )--------------------------------------- */
+
+
+ /**
+ * Return a unique string like "maniphest.author" to expose this rule as an
+ * object policy.
+ *
+ * Object policy rules, like "Task Author", are more advanced than basic
+ * policy rules (like "All Users") but not as powerful as custom rules.
+ *
+ * @return string Unique identifier for this rule.
+ * @task objectpolicy
+ */
+ public function getObjectPolicyKey() {
+ return null;
+ }
+
+ public function getObjectPolicyName() {
+ throw new PhutilMethodNotImplementedException();
+ }
+
+ public function getObjectPolicyShortName() {
+ return $this->getObjectPolicyName();
+ }
+
+ public function getObjectPolicyIcon() {
+ return 'fa-cube';
+ }
+
+ public function getPolicyExplanation() {
+ throw new PhutilMethodNotImplementedException();
+ }
+
}
diff --git a/src/applications/policy/storage/PhabricatorPolicy.php b/src/applications/policy/storage/PhabricatorPolicy.php
--- a/src/applications/policy/storage/PhabricatorPolicy.php
+++ b/src/applications/policy/storage/PhabricatorPolicy.php
@@ -54,6 +54,11 @@
return PhabricatorPolicyQuery::getGlobalPolicy($policy_identifier);
}
+ $policy = PhabricatorPolicyQuery::getObjectPolicy($policy_identifier);
+ if ($policy) {
+ return $policy;
+ }
+
if (!$handle) {
throw new Exception(
pht(
@@ -158,7 +163,16 @@
return $this->workflow;
}
+ public function setIcon($icon) {
+ $this->icon = $icon;
+ return $this;
+ }
+
public function getIcon() {
+ if ($this->icon) {
+ return $this->icon;
+ }
+
switch ($this->getType()) {
case PhabricatorPolicyType::TYPE_GLOBAL:
static $map = array(
@@ -204,6 +218,11 @@
PhabricatorUser $viewer,
$policy) {
+ $rule = PhabricatorPolicyQuery::getObjectPolicyRule($policy);
+ if ($rule) {
+ return $rule->getPolicyExplanation();
+ }
+
switch ($policy) {
case PhabricatorPolicies::POLICY_PUBLIC:
return pht('This object is public.');
diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
--- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
+++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
@@ -243,10 +243,10 @@
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
- if (!PhabricatorPolicyQuery::isGlobalPolicy($old)) {
+ if (!PhabricatorPolicyQuery::isSpecialPolicy($old)) {
$phids[] = array($old);
}
- if (!PhabricatorPolicyQuery::isGlobalPolicy($new)) {
+ if (!PhabricatorPolicyQuery::isSpecialPolicy($new)) {
$phids[] = array($new);
}
break;
diff --git a/src/view/form/control/AphrontFormPolicyControl.php b/src/view/form/control/AphrontFormPolicyControl.php
--- a/src/view/form/control/AphrontFormPolicyControl.php
+++ b/src/view/form/control/AphrontFormPolicyControl.php
@@ -7,6 +7,7 @@
private $policies;
private $spacePHID;
private $templatePHIDType;
+ private $templateObject;
public function setPolicyObject(PhabricatorPolicyInterface $object) {
$this->object = $object;
@@ -33,6 +34,11 @@
return $this;
}
+ public function setTemplateObject($object) {
+ $this->templateObject = $object;
+ return $this;
+ }
+
public function setCapability($capability) {
$this->capability = $capability;
@@ -64,9 +70,31 @@
protected function getOptions() {
$capability = $this->capability;
+ $policies = $this->policies;
+
+ // Exclude object policies which don't make sense here. This primarily
+ // filters object policies associated from template capabilities (like
+ // "Default Task View Policy" being set to "Task Author") so they aren't
+ // made available on non-template capabilities (like "Can Bulk Edit").
+ foreach ($policies as $key => $policy) {
+ if ($policy->getType() != PhabricatorPolicyType::TYPE_OBJECT) {
+ continue;
+ }
+
+ $rule = PhabricatorPolicyQuery::getObjectPolicyRule($policy->getPHID());
+ if (!$rule) {
+ continue;
+ }
+
+ $target = nonempty($this->templateObject, $this->object);
+ if (!$rule->canApplyToObject($target)) {
+ unset($policies[$key]);
+ continue;
+ }
+ }
$options = array();
- foreach ($this->policies as $policy) {
+ foreach ($policies as $policy) {
if ($policy->getPHID() == PhabricatorPolicies::POLICY_PUBLIC) {
// Never expose "Public" for capabilities which don't support it.
$capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
@@ -74,6 +102,7 @@
continue;
}
}
+
$policy_short_name = id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs(28)
->truncateString($policy->getName());
@@ -122,6 +151,7 @@
$options,
array(
PhabricatorPolicyType::TYPE_GLOBAL,
+ PhabricatorPolicyType::TYPE_OBJECT,
PhabricatorPolicyType::TYPE_USER,
PhabricatorPolicyType::TYPE_CUSTOM,
PhabricatorPolicyType::TYPE_PROJECT,

File Metadata

Mime Type
text/plain
Expires
Fri, May 10, 11:33 PM (1 w, 2 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6284399
Default Alt Text
D13257.diff (16 KB)

Event Timeline