mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-21 04:50:55 +01:00
Tighten up some policy interactions in Herald
Summary: Ref T603. Herald is a bit of a policy minefield right now, although I think pretty much everything has straightforward solutions. This change: - Introduces "create" and "create global" permisions for Herald. - Maybe "create" is sort of redundant since there's no reason to have access to the application if not creating rules, but I think this won't be the case for most applications, so having an explicit "create" permission is more consistent. - Add some application policy helper functions. - Improve rendering a bit -- I think we probably need to build some `PolicyType` class, similar to `PHIDType`, to really get this right. - Don't let users who can't use application X create Herald rules for application X. - Remove Maniphest/Pholio rules when those applications are not installed. Test Plan: - Restricted access to Maniphest and uninstalled Pholio. - Verified Pholio rules no longer appear for anyone. - Verified Maniphest ruls no longer appear for restricted users. - Verified users without CREATE_GLOBAL can not create global ruls. Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T603 Differential Revision: https://secure.phabricator.com/D7219
This commit is contained in:
parent
a600ab7731
commit
c8127edfe9
15 changed files with 139 additions and 35 deletions
|
@ -350,4 +350,23 @@ abstract class PhabricatorController extends AphrontController {
|
||||||
return $view;
|
return $view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function hasApplicationCapability($capability) {
|
||||||
|
return PhabricatorPolicyFilter::hasCapability(
|
||||||
|
$this->getRequest()->getUser(),
|
||||||
|
$this->getCurrentApplication(),
|
||||||
|
$capability);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function requireApplicationCapability($capability) {
|
||||||
|
PhabricatorPolicyFilter::requireCapability(
|
||||||
|
$this->getRequest()->getUser(),
|
||||||
|
$this->getCurrentApplication(),
|
||||||
|
$capability);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function explainApplicationCapability($capability, $message) {
|
||||||
|
// TODO: Render a link to get more information.
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,6 +97,17 @@ abstract class HeraldAdapter {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isAvailableToUser(PhabricatorUser $viewer) {
|
||||||
|
$applications = id(new PhabricatorApplicationQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withInstalled(true)
|
||||||
|
->withClasses(array($this->getAdapterApplicationClass()))
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
return !empty($applications);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NOTE: You generally should not override this; it exists to support legacy
|
* NOTE: You generally should not override this; it exists to support legacy
|
||||||
* adapters which had hard-coded content types.
|
* adapters which had hard-coded content types.
|
||||||
|
@ -106,6 +117,7 @@ abstract class HeraldAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract public function getAdapterContentName();
|
abstract public function getAdapterContentName();
|
||||||
|
abstract public function getAdapterApplicationClass();
|
||||||
|
|
||||||
|
|
||||||
/* -( Fields )------------------------------------------------------------- */
|
/* -( Fields )------------------------------------------------------------- */
|
||||||
|
@ -694,16 +706,6 @@ abstract class HeraldAdapter {
|
||||||
return $adapters;
|
return $adapters;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getAllEnabledAdapters() {
|
|
||||||
$adapters = self::getAllAdapters();
|
|
||||||
foreach ($adapters as $key => $adapter) {
|
|
||||||
if (!$adapter->isEnabled()) {
|
|
||||||
unset($adapters[$key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $adapters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getAdapterForContentType($content_type) {
|
public static function getAdapterForContentType($content_type) {
|
||||||
$adapters = self::getAllAdapters();
|
$adapters = self::getAllAdapters();
|
||||||
|
|
||||||
|
@ -719,11 +721,14 @@ abstract class HeraldAdapter {
|
||||||
$content_type));
|
$content_type));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getEnabledAdapterMap() {
|
public static function getEnabledAdapterMap(PhabricatorUser $viewer) {
|
||||||
$map = array();
|
$map = array();
|
||||||
|
|
||||||
$adapters = HeraldAdapter::getAllEnabledAdapters();
|
$adapters = HeraldAdapter::getAllAdapters();
|
||||||
foreach ($adapters as $adapter) {
|
foreach ($adapters as $adapter) {
|
||||||
|
if (!$adapter->isAvailableToUser($viewer)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$type = $adapter->getAdapterContentType();
|
$type = $adapter->getAdapterContentType();
|
||||||
$name = $adapter->getAdapterContentName();
|
$name = $adapter->getAdapterContentName();
|
||||||
$map[$type] = $name;
|
$map[$type] = $name;
|
||||||
|
@ -733,7 +738,6 @@ abstract class HeraldAdapter {
|
||||||
return $map;
|
return $map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function renderRuleAsText(HeraldRule $rule, array $handles) {
|
public function renderRuleAsText(HeraldRule $rule, array $handles) {
|
||||||
assert_instances_of($handles, 'PhabricatorObjectHandle');
|
assert_instances_of($handles, 'PhabricatorObjectHandle');
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,8 @@ final class HeraldCommitAdapter extends HeraldAdapter {
|
||||||
protected $affectedPackages;
|
protected $affectedPackages;
|
||||||
protected $auditNeededPackages;
|
protected $auditNeededPackages;
|
||||||
|
|
||||||
public function isEnabled() {
|
public function getAdapterApplicationClass() {
|
||||||
$app = 'PhabricatorApplicationDiffusion';
|
return 'PhabricatorApplicationDiffusion';
|
||||||
return PhabricatorApplication::isClassInstalled($app);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAdapterContentType() {
|
public function getAdapterContentType() {
|
||||||
|
|
|
@ -20,9 +20,8 @@ final class HeraldDifferentialRevisionAdapter extends HeraldAdapter {
|
||||||
protected $affectedPackages;
|
protected $affectedPackages;
|
||||||
protected $changesets;
|
protected $changesets;
|
||||||
|
|
||||||
public function isEnabled() {
|
public function getAdapterApplicationClass() {
|
||||||
$app = 'PhabricatorApplicationDifferential';
|
return 'PhabricatorApplicationDifferential';
|
||||||
return PhabricatorApplication::isClassInstalled($app);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAdapterContentType() {
|
public function getAdapterContentType() {
|
||||||
|
|
|
@ -10,6 +10,10 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter {
|
||||||
private $assignPHID;
|
private $assignPHID;
|
||||||
private $projectPHIDs = array();
|
private $projectPHIDs = array();
|
||||||
|
|
||||||
|
public function getAdapterApplicationClass() {
|
||||||
|
return 'PhabricatorApplicationManiphest';
|
||||||
|
}
|
||||||
|
|
||||||
public function setTask(ManiphestTask $task) {
|
public function setTask(ManiphestTask $task) {
|
||||||
$this->task = $task;
|
$this->task = $task;
|
||||||
return $this;
|
return $this;
|
||||||
|
|
|
@ -8,6 +8,10 @@ final class HeraldPholioMockAdapter extends HeraldAdapter {
|
||||||
private $mock;
|
private $mock;
|
||||||
private $ccPHIDs = array();
|
private $ccPHIDs = array();
|
||||||
|
|
||||||
|
public function getAdapterApplicationClass() {
|
||||||
|
return 'PhabricatorApplicationPholio';
|
||||||
|
}
|
||||||
|
|
||||||
public function setMock(PholioMock $mock) {
|
public function setMock(PholioMock $mock) {
|
||||||
$this->mock = $mock;
|
$this->mock = $mock;
|
||||||
return $this;
|
return $this;
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
final class PhabricatorApplicationHerald extends PhabricatorApplication {
|
final class PhabricatorApplicationHerald extends PhabricatorApplication {
|
||||||
|
|
||||||
|
const CAN_CREATE_RULE = 'herald.create';
|
||||||
|
const CAN_CREATE_GLOBAL_RULE = 'herald.global';
|
||||||
|
|
||||||
public function getBaseURI() {
|
public function getBaseURI() {
|
||||||
return '/herald/';
|
return '/herald/';
|
||||||
}
|
}
|
||||||
|
@ -48,4 +51,18 @@ final class PhabricatorApplicationHerald extends PhabricatorApplication {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getCustomCapabilities() {
|
||||||
|
return array(
|
||||||
|
self::CAN_CREATE_RULE => array(
|
||||||
|
'label' => pht('Can Create Rules'),
|
||||||
|
),
|
||||||
|
self::CAN_CREATE_GLOBAL_RULE => array(
|
||||||
|
'label' => pht('Can Create Global Rules'),
|
||||||
|
'caption' => pht('Global rules can bypass access controls.'),
|
||||||
|
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,15 @@ abstract class HeraldController extends PhabricatorController {
|
||||||
public function buildApplicationCrumbs() {
|
public function buildApplicationCrumbs() {
|
||||||
$crumbs = parent::buildApplicationCrumbs();
|
$crumbs = parent::buildApplicationCrumbs();
|
||||||
|
|
||||||
|
$can_create = $this->hasApplicationCapability(
|
||||||
|
PhabricatorApplicationHerald::CAN_CREATE_RULE);
|
||||||
|
|
||||||
$crumbs->addAction(
|
$crumbs->addAction(
|
||||||
id(new PHUIListItemView())
|
id(new PHUIListItemView())
|
||||||
->setName(pht('Create Herald Rule'))
|
->setName(pht('Create Herald Rule'))
|
||||||
->setHref($this->getApplicationURI('new/'))
|
->setHref($this->getApplicationURI('new/'))
|
||||||
->setIcon('create'));
|
->setIcon('create')
|
||||||
|
->setDisabled(!$can_create));
|
||||||
|
|
||||||
return $crumbs;
|
return $crumbs;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,16 @@ final class HeraldNewController extends HeraldController {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function processRequest() {
|
public function processRequest() {
|
||||||
|
|
||||||
$request = $this->getRequest();
|
$request = $this->getRequest();
|
||||||
$user = $request->getUser();
|
$user = $request->getUser();
|
||||||
|
|
||||||
$content_type_map = HeraldAdapter::getEnabledAdapterMap();
|
$this->requireApplicationCapability(
|
||||||
|
PhabricatorApplicationHerald::CAN_CREATE_RULE);
|
||||||
|
|
||||||
|
$can_global = $this->hasApplicationCapability(
|
||||||
|
PhabricatorApplicationHerald::CAN_CREATE_GLOBAL_RULE);
|
||||||
|
|
||||||
|
$content_type_map = HeraldAdapter::getEnabledAdapterMap($user);
|
||||||
if (empty($content_type_map[$this->contentType])) {
|
if (empty($content_type_map[$this->contentType])) {
|
||||||
$this->contentType = head_key($content_type_map);
|
$this->contentType = head_key($content_type_map);
|
||||||
}
|
}
|
||||||
|
@ -32,14 +37,29 @@ final class HeraldNewController extends HeraldController {
|
||||||
HeraldRuleTypeConfig::RULE_TYPE_PERSONAL,
|
HeraldRuleTypeConfig::RULE_TYPE_PERSONAL,
|
||||||
)) + $rule_type_map;
|
)) + $rule_type_map;
|
||||||
|
|
||||||
|
if (!$can_global) {
|
||||||
|
$global_link = $this->explainApplicationCapability(
|
||||||
|
PhabricatorApplicationHerald::CAN_CREATE_GLOBAL_RULE,
|
||||||
|
pht('You do not have permission to create or manage global rules.'));
|
||||||
|
} else {
|
||||||
|
$global_link = null;
|
||||||
|
}
|
||||||
|
|
||||||
$captions = array(
|
$captions = array(
|
||||||
HeraldRuleTypeConfig::RULE_TYPE_PERSONAL =>
|
HeraldRuleTypeConfig::RULE_TYPE_PERSONAL =>
|
||||||
pht('Personal rules notify you about events. You own them, but '.
|
pht(
|
||||||
'they can only affect you.'),
|
'Personal rules notify you about events. You own them, but they can '.
|
||||||
|
'only affect you.'),
|
||||||
HeraldRuleTypeConfig::RULE_TYPE_GLOBAL =>
|
HeraldRuleTypeConfig::RULE_TYPE_GLOBAL =>
|
||||||
pht('Global rules notify anyone about events. No one owns them, and '.
|
phutil_implode_html(
|
||||||
'anyone can edit them. Usually, Global rules are used to notify '.
|
phutil_tag('br'),
|
||||||
'mailing lists.'),
|
array_filter(
|
||||||
|
array(
|
||||||
|
pht(
|
||||||
|
'Global rules notify anyone about events. Global rules can '.
|
||||||
|
'bypass access control policies.'),
|
||||||
|
$global_link,
|
||||||
|
))),
|
||||||
);
|
);
|
||||||
|
|
||||||
$radio = id(new AphrontFormRadioButtonControl())
|
$radio = id(new AphrontFormRadioButtonControl())
|
||||||
|
@ -48,10 +68,15 @@ final class HeraldNewController extends HeraldController {
|
||||||
->setValue($this->ruleType);
|
->setValue($this->ruleType);
|
||||||
|
|
||||||
foreach ($rule_type_map as $value => $name) {
|
foreach ($rule_type_map as $value => $name) {
|
||||||
|
$disabled = ($value == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) &&
|
||||||
|
(!$can_global);
|
||||||
|
|
||||||
$radio->addButton(
|
$radio->addButton(
|
||||||
$value,
|
$value,
|
||||||
$name,
|
$name,
|
||||||
idx($captions, $value));
|
idx($captions, $value),
|
||||||
|
$disabled ? 'disabled' : null,
|
||||||
|
$disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
$form = id(new AphrontFormView())
|
$form = id(new AphrontFormView())
|
||||||
|
|
|
@ -14,7 +14,7 @@ final class HeraldRuleController extends HeraldController {
|
||||||
$request = $this->getRequest();
|
$request = $this->getRequest();
|
||||||
$user = $request->getUser();
|
$user = $request->getUser();
|
||||||
|
|
||||||
$content_type_map = HeraldAdapter::getEnabledAdapterMap();
|
$content_type_map = HeraldAdapter::getEnabledAdapterMap($user);
|
||||||
$rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap();
|
$rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap();
|
||||||
|
|
||||||
if ($this->id) {
|
if ($this->id) {
|
||||||
|
@ -42,11 +42,19 @@ final class HeraldRuleController extends HeraldController {
|
||||||
|
|
||||||
$rule_type = $request->getStr('rule_type');
|
$rule_type = $request->getStr('rule_type');
|
||||||
if (!isset($rule_type_map[$rule_type])) {
|
if (!isset($rule_type_map[$rule_type])) {
|
||||||
$rule_type = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL;
|
$rule_type = HeraldRuleTypeConfig::RULE_TYPE_PERSONAL;
|
||||||
}
|
}
|
||||||
$rule->setRuleType($rule_type);
|
$rule->setRuleType($rule_type);
|
||||||
|
|
||||||
$cancel_uri = $this->getApplicationURI();
|
$cancel_uri = $this->getApplicationURI();
|
||||||
|
|
||||||
|
$this->requireApplicationCapability(
|
||||||
|
PhabricatorApplicationHerald::CAN_CREATE_RULE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) {
|
||||||
|
$this->requireApplicationCapability(
|
||||||
|
PhabricatorApplicationHerald::CAN_CREATE_GLOBAL_RULE);
|
||||||
}
|
}
|
||||||
|
|
||||||
$adapter = HeraldAdapter::getAdapterForContentType($rule->getContentType());
|
$adapter = HeraldAdapter::getAdapterForContentType($rule->getContentType());
|
||||||
|
|
|
@ -33,7 +33,7 @@ final class HeraldRuleListController extends HeraldController
|
||||||
$phids = mpull($rules, 'getAuthorPHID');
|
$phids = mpull($rules, 'getAuthorPHID');
|
||||||
$this->loadHandles($phids);
|
$this->loadHandles($phids);
|
||||||
|
|
||||||
$content_type_map = HeraldAdapter::getEnabledAdapterMap();
|
$content_type_map = HeraldAdapter::getEnabledAdapterMap($viewer);
|
||||||
|
|
||||||
$list = id(new PHUIObjectItemListView())
|
$list = id(new PHUIObjectItemListView())
|
||||||
->setUser($viewer);
|
->setUser($viewer);
|
||||||
|
|
|
@ -96,7 +96,9 @@ final class HeraldRuleViewController extends HeraldController {
|
||||||
if ($adapter) {
|
if ($adapter) {
|
||||||
$view->addProperty(
|
$view->addProperty(
|
||||||
pht('Applies To'),
|
pht('Applies To'),
|
||||||
idx(HeraldAdapter::getEnabledAdapterMap(), $rule->getContentType()));
|
idx(
|
||||||
|
HeraldAdapter::getEnabledAdapterMap($viewer),
|
||||||
|
$rule->getContentType()));
|
||||||
|
|
||||||
$view->invokeWillRenderEvent();
|
$view->invokeWillRenderEvent();
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,16 @@ final class HeraldRuleQuery
|
||||||
public function willFilterPage(array $rules) {
|
public function willFilterPage(array $rules) {
|
||||||
$rule_ids = mpull($rules, 'getID');
|
$rule_ids = mpull($rules, 'getID');
|
||||||
|
|
||||||
|
// Filter out any rules that have invalid adapters, or have adapters the
|
||||||
|
// viewer isn't permitted to see or use (for example, Differential rules
|
||||||
|
// if the user can't use Differential or Differential is not installed).
|
||||||
|
$types = HeraldAdapter::getEnabledAdapterMap($this->getViewer());
|
||||||
|
foreach ($rules as $key => $rule) {
|
||||||
|
if (empty($types[$rule->getContentType()])) {
|
||||||
|
unset($rules[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->needValidateAuthors) {
|
if ($this->needValidateAuthors) {
|
||||||
$this->validateRuleAuthors($rules);
|
$this->validateRuleAuthors($rules);
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,11 +110,13 @@ final class HeraldRuleSearchEngine
|
||||||
private function getContentTypeOptions() {
|
private function getContentTypeOptions() {
|
||||||
return array(
|
return array(
|
||||||
'' => pht('(All Content Types)'),
|
'' => pht('(All Content Types)'),
|
||||||
) + HeraldAdapter::getEnabledAdapterMap();
|
) + HeraldAdapter::getEnabledAdapterMap($this->requireViewer());
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getContentTypeValues() {
|
private function getContentTypeValues() {
|
||||||
return array_fuse(array_keys(HeraldAdapter::getEnabledAdapterMap()));
|
return array_fuse(
|
||||||
|
array_keys(
|
||||||
|
HeraldAdapter::getEnabledAdapterMap($this->requireViewer())));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getRuleTypeOptions() {
|
private function getRuleTypeOptions() {
|
||||||
|
|
|
@ -281,6 +281,13 @@ final class PhabricatorPolicyFilter {
|
||||||
case PhabricatorPolicyCapability::CAN_JOIN:
|
case PhabricatorPolicyCapability::CAN_JOIN:
|
||||||
$message = pht('You do not have permission to join this object.');
|
$message = pht('You do not have permission to join this object.');
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
// TODO: Farm these out to applications?
|
||||||
|
$message = pht(
|
||||||
|
'You do not have a required capability ("%s") to do whatever you '.
|
||||||
|
'are trying to do.',
|
||||||
|
$capability);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($policy) {
|
switch ($policy) {
|
||||||
|
@ -369,7 +376,7 @@ final class PhabricatorPolicyFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
$more = array_merge(
|
$more = array_merge(
|
||||||
array($more),
|
array_filter(array($more)),
|
||||||
array_filter((array)$object->describeAutomaticCapability($capability)));
|
array_filter((array)$object->describeAutomaticCapability($capability)));
|
||||||
|
|
||||||
$exception = new PhabricatorPolicyException($message);
|
$exception = new PhabricatorPolicyException($message);
|
||||||
|
|
Loading…
Reference in a new issue