1
0
Fork 0
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:
epriestley 2015-06-13 15:44:38 -07:00
parent 7f98a8575d
commit 466755476a
9 changed files with 300 additions and 8 deletions

View file

@ -3,6 +3,18 @@
final class ManiphestTaskAuthorPolicyRule final class ManiphestTaskAuthorPolicyRule
extends PhabricatorPolicyRule { 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() { public function getRuleDescription() {
return pht('task author'); return pht('task author');
} }

View file

@ -136,6 +136,20 @@ final class PhabricatorApplicationEditController
$template = $application->getCapabilityTemplatePHIDType($capability); $template = $application->getCapabilityTemplatePHIDType($capability);
if ($template) { 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); $control->setTemplatePHIDType($template);
} }

View file

@ -3,6 +3,7 @@
final class PhabricatorPolicyType extends PhabricatorPolicyConstants { final class PhabricatorPolicyType extends PhabricatorPolicyConstants {
const TYPE_GLOBAL = 'global'; const TYPE_GLOBAL = 'global';
const TYPE_OBJECT = 'object';
const TYPE_USER = 'user'; const TYPE_USER = 'user';
const TYPE_CUSTOM = 'custom'; const TYPE_CUSTOM = 'custom';
const TYPE_PROJECT = 'project'; const TYPE_PROJECT = 'project';
@ -11,9 +12,10 @@ final class PhabricatorPolicyType extends PhabricatorPolicyConstants {
public static function getPolicyTypeOrder($type) { public static function getPolicyTypeOrder($type) {
static $map = array( static $map = array(
self::TYPE_GLOBAL => 0, self::TYPE_GLOBAL => 0,
self::TYPE_USER => 1, self::TYPE_OBJECT => 1,
self::TYPE_CUSTOM => 2, self::TYPE_USER => 2,
self::TYPE_PROJECT => 3, self::TYPE_CUSTOM => 3,
self::TYPE_PROJECT => 4,
self::TYPE_MASKED => 9, self::TYPE_MASKED => 9,
); );
return idx($map, $type, 9); return idx($map, $type, 9);
@ -23,6 +25,8 @@ final class PhabricatorPolicyType extends PhabricatorPolicyConstants {
switch ($type) { switch ($type) {
case self::TYPE_GLOBAL: case self::TYPE_GLOBAL:
return pht('Basic Policies'); return pht('Basic Policies');
case self::TYPE_OBJECT:
return pht('Object Policies');
case self::TYPE_USER: case self::TYPE_USER:
return pht('User Policies'); return pht('User Policies');
case self::TYPE_CUSTOM: case self::TYPE_CUSTOM:

View file

@ -8,6 +8,7 @@ final class PhabricatorPolicyFilter {
private $raisePolicyExceptions; private $raisePolicyExceptions;
private $userProjects; private $userProjects;
private $customPolicies = array(); private $customPolicies = array();
private $objectPolicies = array();
private $forcedPolicy; private $forcedPolicy;
public static function mustRetainCapability( public static function mustRetainCapability(
@ -131,6 +132,7 @@ final class PhabricatorPolicyFilter {
$need_projects = array(); $need_projects = array();
$need_policies = array(); $need_policies = array();
$need_objpolicies = array();
foreach ($objects as $key => $object) { foreach ($objects as $key => $object) {
$object_capabilities = $object->getCapabilities(); $object_capabilities = $object->getCapabilities();
foreach ($capabilities as $capability) { foreach ($capabilities as $capability) {
@ -143,17 +145,29 @@ final class PhabricatorPolicyFilter {
} }
$policy = $this->getObjectPolicy($object, $capability); $policy = $this->getObjectPolicy($object, $capability);
if (PhabricatorPolicyQuery::isObjectPolicy($policy)) {
$need_objpolicies[$policy][] = $object;
continue;
}
$type = phid_get_type($policy); $type = phid_get_type($policy);
if ($type == PhabricatorProjectProjectPHIDType::TYPECONST) { if ($type == PhabricatorProjectProjectPHIDType::TYPECONST) {
$need_projects[$policy] = $policy; $need_projects[$policy] = $policy;
continue;
} }
if ($type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) { if ($type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
$need_policies[$policy][] = $object; $need_policies[$policy][] = $object;
continue;
} }
} }
} }
if ($need_objpolicies) {
$this->loadObjectPolicies($need_objpolicies);
}
if ($need_policies) { if ($need_policies) {
$this->loadCustomPolicies($need_policies); $this->loadCustomPolicies($need_policies);
} }
@ -486,6 +500,15 @@ final class PhabricatorPolicyFilter {
$this->rejectObject($object, $policy, $capability); $this->rejectObject($object, $policy, $capability);
break; break;
default: default:
if (PhabricatorPolicyQuery::isObjectPolicy($policy)) {
if ($this->checkObjectPolicy($policy, $object)) {
return true;
} else {
$this->rejectObject($object, $policy, $capability);
break;
}
}
$type = phid_get_type($policy); $type = phid_get_type($policy);
if ($type == PhabricatorProjectProjectPHIDType::TYPECONST) { if ($type == PhabricatorProjectProjectPHIDType::TYPECONST) {
if (!empty($this->userProjects[$viewer->getPHID()][$policy])) { if (!empty($this->userProjects[$viewer->getPHID()][$policy])) {
@ -573,6 +596,32 @@ final class PhabricatorPolicyFilter {
throw $exception; 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) { private function loadCustomPolicies(array $map) {
$viewer = $this->viewer; $viewer = $this->viewer;
$viewer_phid = $viewer->getPHID(); $viewer_phid = $viewer->getPHID();
@ -627,6 +676,24 @@ final class PhabricatorPolicyFilter {
$this->customPolicies[$viewer->getPHID()] += $custom_policies; $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( private function checkCustomPolicy(
$policy_phid, $policy_phid,
PhabricatorPolicyInterface $object) { PhabricatorPolicyInterface $object) {

View file

@ -6,6 +6,8 @@ final class PhabricatorPolicyQuery
private $object; private $object;
private $phids; private $phids;
const OBJECT_POLICY_PREFIX = 'obj.';
public function setObject(PhabricatorPolicyInterface $object) { public function setObject(PhabricatorPolicyInterface $object) {
$this->object = $object; $this->object = $object;
return $this; return $this;
@ -71,7 +73,15 @@ final class PhabricatorPolicyQuery
$results = array(); $results = array();
// First, load global policies. // 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])) { if (isset($phids[$phid])) {
$results[$phid] = $policy; $results[$phid] = $policy;
unset($phids[$phid]); unset($phids[$phid]);
@ -212,7 +222,7 @@ final class PhabricatorPolicyQuery
// option unless the object already has a "Public" policy. In this case we // 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". // retain the policy but enforce it as though it was "All Users".
$show_public = PhabricatorEnv::getEnvConfig('policy.allow-public'); $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 ($phid == PhabricatorPolicies::POLICY_PUBLIC) {
if (!$show_public) { if (!$show_public) {
continue; continue;
@ -221,6 +231,10 @@ final class PhabricatorPolicyQuery
$phids[] = $phid; $phids[] = $phid;
} }
foreach (self::getObjectPolicies($this->object) as $phid => $policy) {
$phids[] = $phid;
}
return $phids; return $phids;
} }
@ -234,4 +248,99 @@ final class PhabricatorPolicyQuery
return 'PhabricatorPolicyApplication'; 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;
}
} }

View file

@ -1,5 +1,8 @@
<?php <?php
/**
* @task objectpolicy Implementing Object Policies
*/
abstract class PhabricatorPolicyRule { abstract class PhabricatorPolicyRule {
const CONTROL_TYPE_TEXT = 'text'; const CONTROL_TYPE_TEXT = 'text';
@ -93,4 +96,38 @@ abstract class PhabricatorPolicyRule {
return true; 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();
}
} }

View file

@ -54,6 +54,11 @@ final class PhabricatorPolicy
return PhabricatorPolicyQuery::getGlobalPolicy($policy_identifier); return PhabricatorPolicyQuery::getGlobalPolicy($policy_identifier);
} }
$policy = PhabricatorPolicyQuery::getObjectPolicy($policy_identifier);
if ($policy) {
return $policy;
}
if (!$handle) { if (!$handle) {
throw new Exception( throw new Exception(
pht( pht(
@ -158,7 +163,16 @@ final class PhabricatorPolicy
return $this->workflow; return $this->workflow;
} }
public function setIcon($icon) {
$this->icon = $icon;
return $this;
}
public function getIcon() { public function getIcon() {
if ($this->icon) {
return $this->icon;
}
switch ($this->getType()) { switch ($this->getType()) {
case PhabricatorPolicyType::TYPE_GLOBAL: case PhabricatorPolicyType::TYPE_GLOBAL:
static $map = array( static $map = array(
@ -204,6 +218,11 @@ final class PhabricatorPolicy
PhabricatorUser $viewer, PhabricatorUser $viewer,
$policy) { $policy) {
$rule = PhabricatorPolicyQuery::getObjectPolicyRule($policy);
if ($rule) {
return $rule->getPolicyExplanation();
}
switch ($policy) { switch ($policy) {
case PhabricatorPolicies::POLICY_PUBLIC: case PhabricatorPolicies::POLICY_PUBLIC:
return pht('This object is public.'); return pht('This object is public.');

View file

@ -243,10 +243,10 @@ abstract class PhabricatorApplicationTransaction
case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY:
if (!PhabricatorPolicyQuery::isGlobalPolicy($old)) { if (!PhabricatorPolicyQuery::isSpecialPolicy($old)) {
$phids[] = array($old); $phids[] = array($old);
} }
if (!PhabricatorPolicyQuery::isGlobalPolicy($new)) { if (!PhabricatorPolicyQuery::isSpecialPolicy($new)) {
$phids[] = array($new); $phids[] = array($new);
} }
break; break;

View file

@ -7,6 +7,7 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
private $policies; private $policies;
private $spacePHID; private $spacePHID;
private $templatePHIDType; private $templatePHIDType;
private $templateObject;
public function setPolicyObject(PhabricatorPolicyInterface $object) { public function setPolicyObject(PhabricatorPolicyInterface $object) {
$this->object = $object; $this->object = $object;
@ -33,6 +34,11 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
return $this; return $this;
} }
public function setTemplateObject($object) {
$this->templateObject = $object;
return $this;
}
public function setCapability($capability) { public function setCapability($capability) {
$this->capability = $capability; $this->capability = $capability;
@ -64,9 +70,31 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
protected function getOptions() { protected function getOptions() {
$capability = $this->capability; $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(); $options = array();
foreach ($this->policies as $policy) { foreach ($policies as $policy) {
if ($policy->getPHID() == PhabricatorPolicies::POLICY_PUBLIC) { if ($policy->getPHID() == PhabricatorPolicies::POLICY_PUBLIC) {
// Never expose "Public" for capabilities which don't support it. // Never expose "Public" for capabilities which don't support it.
$capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability); $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
@ -74,6 +102,7 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
continue; continue;
} }
} }
$policy_short_name = id(new PhutilUTF8StringTruncator()) $policy_short_name = id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs(28) ->setMaximumGlyphs(28)
->truncateString($policy->getName()); ->truncateString($policy->getName());
@ -122,6 +151,7 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
$options, $options,
array( array(
PhabricatorPolicyType::TYPE_GLOBAL, PhabricatorPolicyType::TYPE_GLOBAL,
PhabricatorPolicyType::TYPE_OBJECT,
PhabricatorPolicyType::TYPE_USER, PhabricatorPolicyType::TYPE_USER,
PhabricatorPolicyType::TYPE_CUSTOM, PhabricatorPolicyType::TYPE_CUSTOM,
PhabricatorPolicyType::TYPE_PROJECT, PhabricatorPolicyType::TYPE_PROJECT,