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
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');
}

View file

@ -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);
}

View file

@ -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:

View file

@ -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) {

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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.');

View file

@ -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;

View file

@ -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,