mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-21 04:50:55 +01:00
Allow PolicyRules to serve as "Object Policies"
Summary: Ref T5681. Ref T8488. This allows policy rules to provide "Object Policies", which are similar to the global/basic policies: - They show up directly in the dropdown (you don't have to create a custom rule). - They don't need to create or load anything in the database. To implement one, you just add a couple methods on an existing PolicyRule that let Phabricator know it can work as an object policy rule. {F494764} These rules only show up where they make sense. For example, the "Task Author" rule is only available in Maniphest, and in "Default View Policy" / "Default Edit Policy" of the Application config. This should make T8488 easier by letting us set the default policies to "Members of Thread", without having to create a dedicated custom policy for every thread. Test Plan: - Set tasks to "Task Author" policy. - Tried to view them as other users. - Viewed transaction change strings. - Viewed policy errors. - Set them as default policies. - Verified they don't leak into other policy controls. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T5681, T8488 Differential Revision: https://secure.phabricator.com/D13257
This commit is contained in:
parent
7f98a8575d
commit
466755476a
9 changed files with 300 additions and 8 deletions
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -136,6 +136,20 @@ final class PhabricatorApplicationEditController
|
|||
|
||||
$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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 @@ final class PhabricatorPolicyType extends PhabricatorPolicyConstants {
|
|||
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 @@ final class PhabricatorPolicyType extends PhabricatorPolicyConstants {
|
|||
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:
|
||||
|
|
|
@ -8,6 +8,7 @@ final class PhabricatorPolicyFilter {
|
|||
private $raisePolicyExceptions;
|
||||
private $userProjects;
|
||||
private $customPolicies = array();
|
||||
private $objectPolicies = array();
|
||||
private $forcedPolicy;
|
||||
|
||||
public static function mustRetainCapability(
|
||||
|
@ -131,6 +132,7 @@ final class PhabricatorPolicyFilter {
|
|||
|
||||
$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 @@ final class PhabricatorPolicyFilter {
|
|||
}
|
||||
|
||||
$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 @@ final class PhabricatorPolicyFilter {
|
|||
$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 @@ final class PhabricatorPolicyFilter {
|
|||
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 @@ final class PhabricatorPolicyFilter {
|
|||
$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) {
|
||||
|
|
|
@ -6,6 +6,8 @@ final class PhabricatorPolicyQuery
|
|||
private $object;
|
||||
private $phids;
|
||||
|
||||
const OBJECT_POLICY_PREFIX = 'obj.';
|
||||
|
||||
public function setObject(PhabricatorPolicyInterface $object) {
|
||||
$this->object = $object;
|
||||
return $this;
|
||||
|
@ -71,7 +73,15 @@ final class PhabricatorPolicyQuery
|
|||
$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 @@ final class PhabricatorPolicyQuery
|
|||
// 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 @@ final class PhabricatorPolicyQuery
|
|||
$phids[] = $phid;
|
||||
}
|
||||
|
||||
foreach (self::getObjectPolicies($this->object) as $phid => $policy) {
|
||||
$phids[] = $phid;
|
||||
}
|
||||
|
||||
return $phids;
|
||||
}
|
||||
|
||||
|
@ -234,4 +248,99 @@ final class PhabricatorPolicyQuery
|
|||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @task objectpolicy Implementing Object Policies
|
||||
*/
|
||||
abstract class PhabricatorPolicyRule {
|
||||
|
||||
const CONTROL_TYPE_TEXT = 'text';
|
||||
|
@ -93,4 +96,38 @@ abstract class PhabricatorPolicyRule {
|
|||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -54,6 +54,11 @@ final class PhabricatorPolicy
|
|||
return PhabricatorPolicyQuery::getGlobalPolicy($policy_identifier);
|
||||
}
|
||||
|
||||
$policy = PhabricatorPolicyQuery::getObjectPolicy($policy_identifier);
|
||||
if ($policy) {
|
||||
return $policy;
|
||||
}
|
||||
|
||||
if (!$handle) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
|
@ -158,7 +163,16 @@ final class PhabricatorPolicy
|
|||
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 @@ final class PhabricatorPolicy
|
|||
PhabricatorUser $viewer,
|
||||
$policy) {
|
||||
|
||||
$rule = PhabricatorPolicyQuery::getObjectPolicyRule($policy);
|
||||
if ($rule) {
|
||||
return $rule->getPolicyExplanation();
|
||||
}
|
||||
|
||||
switch ($policy) {
|
||||
case PhabricatorPolicies::POLICY_PUBLIC:
|
||||
return pht('This object is public.');
|
||||
|
|
|
@ -243,10 +243,10 @@ abstract class PhabricatorApplicationTransaction
|
|||
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;
|
||||
|
|
|
@ -7,6 +7,7 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
|
|||
private $policies;
|
||||
private $spacePHID;
|
||||
private $templatePHIDType;
|
||||
private $templateObject;
|
||||
|
||||
public function setPolicyObject(PhabricatorPolicyInterface $object) {
|
||||
$this->object = $object;
|
||||
|
@ -33,6 +34,11 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setTemplateObject($object) {
|
||||
$this->templateObject = $object;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCapability($capability) {
|
||||
$this->capability = $capability;
|
||||
|
||||
|
@ -64,9 +70,31 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
|
|||
|
||||
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 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$policy_short_name = id(new PhutilUTF8StringTruncator())
|
||||
->setMaximumGlyphs(28)
|
||||
->truncateString($policy->getName());
|
||||
|
@ -122,6 +151,7 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
|
|||
$options,
|
||||
array(
|
||||
PhabricatorPolicyType::TYPE_GLOBAL,
|
||||
PhabricatorPolicyType::TYPE_OBJECT,
|
||||
PhabricatorPolicyType::TYPE_USER,
|
||||
PhabricatorPolicyType::TYPE_CUSTOM,
|
||||
PhabricatorPolicyType::TYPE_PROJECT,
|
||||
|
|
Loading…
Reference in a new issue