mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 08:42:41 +01:00
Allow applications to define new policy capabilities
Summary: Ref T603. I want to let applications define new capabilities (like "can manage global rules" in Herald) and get full support for them, including reasonable error strings in the UI. Currently, this is difficult for a couple of reasons. Partly this is just a code organization issue, which is easy to fix. The bigger thing is that we have a bunch of strings which depend on both the policy and capability, like: "You must be an administrator to view this object." "Administrator" is the policy, and "view" is the capability. That means every new capability has to add a string for each policy, and every new policy (should we introduce any) needs to add a string for each capability. And we can't do any piecemeal "You must be a {$role} to {$action} this object" becuase it's impossible to translate. Instead, make all the strings depend on //only// the policy, //only// the capability, or //only// the object type. This makes the dialogs read a little more strangely, but I think it's still pretty easy to understand, and it makes adding new stuff way way easier. Also provide more context, and more useful exception messages. Test Plan: - See screenshots. - Also triggered a policy exception and verified it was dramatically more useful than it used to be. Reviewers: btrahan, chad Reviewed By: btrahan CC: chad, aran Maniphest Tasks: T603 Differential Revision: https://secure.phabricator.com/D7260
This commit is contained in:
parent
68c854b967
commit
b1b1ff83f2
15 changed files with 330 additions and 227 deletions
|
@ -836,7 +836,7 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'aphront-dialog-view-css' =>
|
'aphront-dialog-view-css' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/609ccc78/rsrc/css/aphront/dialog-view.css',
|
'uri' => '/res/830fa2de/rsrc/css/aphront/dialog-view.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
|
@ -4184,7 +4184,7 @@ celerity_register_resource_map(array(
|
||||||
), array(
|
), array(
|
||||||
'packages' =>
|
'packages' =>
|
||||||
array(
|
array(
|
||||||
'cd37aa53' =>
|
'c98eaabf' =>
|
||||||
array(
|
array(
|
||||||
'name' => 'core.pkg.css',
|
'name' => 'core.pkg.css',
|
||||||
'symbols' =>
|
'symbols' =>
|
||||||
|
@ -4233,7 +4233,7 @@ celerity_register_resource_map(array(
|
||||||
41 => 'phabricator-tag-view-css',
|
41 => 'phabricator-tag-view-css',
|
||||||
42 => 'phui-list-view-css',
|
42 => 'phui-list-view-css',
|
||||||
),
|
),
|
||||||
'uri' => '/res/pkg/cd37aa53/core.pkg.css',
|
'uri' => '/res/pkg/c98eaabf/core.pkg.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
),
|
),
|
||||||
'64eeda79' =>
|
'64eeda79' =>
|
||||||
|
@ -4425,15 +4425,15 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'reverse' =>
|
'reverse' =>
|
||||||
array(
|
array(
|
||||||
'aphront-dialog-view-css' => 'cd37aa53',
|
'aphront-dialog-view-css' => 'c98eaabf',
|
||||||
'aphront-error-view-css' => 'cd37aa53',
|
'aphront-error-view-css' => 'c98eaabf',
|
||||||
'aphront-list-filter-view-css' => 'cd37aa53',
|
'aphront-list-filter-view-css' => 'c98eaabf',
|
||||||
'aphront-pager-view-css' => 'cd37aa53',
|
'aphront-pager-view-css' => 'c98eaabf',
|
||||||
'aphront-panel-view-css' => 'cd37aa53',
|
'aphront-panel-view-css' => 'c98eaabf',
|
||||||
'aphront-table-view-css' => 'cd37aa53',
|
'aphront-table-view-css' => 'c98eaabf',
|
||||||
'aphront-tokenizer-control-css' => 'cd37aa53',
|
'aphront-tokenizer-control-css' => 'c98eaabf',
|
||||||
'aphront-tooltip-css' => 'cd37aa53',
|
'aphront-tooltip-css' => 'c98eaabf',
|
||||||
'aphront-typeahead-control-css' => 'cd37aa53',
|
'aphront-typeahead-control-css' => 'c98eaabf',
|
||||||
'differential-changeset-view-css' => '4dc2311c',
|
'differential-changeset-view-css' => '4dc2311c',
|
||||||
'differential-core-view-css' => '4dc2311c',
|
'differential-core-view-css' => '4dc2311c',
|
||||||
'differential-inline-comment-editor' => '5e9e5c4e',
|
'differential-inline-comment-editor' => '5e9e5c4e',
|
||||||
|
@ -4447,7 +4447,7 @@ celerity_register_resource_map(array(
|
||||||
'differential-table-of-contents-css' => '4dc2311c',
|
'differential-table-of-contents-css' => '4dc2311c',
|
||||||
'diffusion-commit-view-css' => 'c8ce2d88',
|
'diffusion-commit-view-css' => 'c8ce2d88',
|
||||||
'diffusion-icons-css' => 'c8ce2d88',
|
'diffusion-icons-css' => 'c8ce2d88',
|
||||||
'global-drag-and-drop-css' => 'cd37aa53',
|
'global-drag-and-drop-css' => 'c98eaabf',
|
||||||
'inline-comment-summary-css' => '4dc2311c',
|
'inline-comment-summary-css' => '4dc2311c',
|
||||||
'javelin-aphlict' => '64eeda79',
|
'javelin-aphlict' => '64eeda79',
|
||||||
'javelin-behavior' => '9564fa17',
|
'javelin-behavior' => '9564fa17',
|
||||||
|
@ -4522,56 +4522,56 @@ celerity_register_resource_map(array(
|
||||||
'javelin-util' => '9564fa17',
|
'javelin-util' => '9564fa17',
|
||||||
'javelin-vector' => '9564fa17',
|
'javelin-vector' => '9564fa17',
|
||||||
'javelin-workflow' => '9564fa17',
|
'javelin-workflow' => '9564fa17',
|
||||||
'lightbox-attachment-css' => 'cd37aa53',
|
'lightbox-attachment-css' => 'c98eaabf',
|
||||||
'maniphest-task-summary-css' => '49898640',
|
'maniphest-task-summary-css' => '49898640',
|
||||||
'phabricator-action-list-view-css' => 'cd37aa53',
|
'phabricator-action-list-view-css' => 'c98eaabf',
|
||||||
'phabricator-application-launch-view-css' => 'cd37aa53',
|
'phabricator-application-launch-view-css' => 'c98eaabf',
|
||||||
'phabricator-busy' => '64eeda79',
|
'phabricator-busy' => '64eeda79',
|
||||||
'phabricator-content-source-view-css' => '4dc2311c',
|
'phabricator-content-source-view-css' => '4dc2311c',
|
||||||
'phabricator-core-css' => 'cd37aa53',
|
'phabricator-core-css' => 'c98eaabf',
|
||||||
'phabricator-crumbs-view-css' => 'cd37aa53',
|
'phabricator-crumbs-view-css' => 'c98eaabf',
|
||||||
'phabricator-drag-and-drop-file-upload' => '5e9e5c4e',
|
'phabricator-drag-and-drop-file-upload' => '5e9e5c4e',
|
||||||
'phabricator-dropdown-menu' => '64eeda79',
|
'phabricator-dropdown-menu' => '64eeda79',
|
||||||
'phabricator-file-upload' => '64eeda79',
|
'phabricator-file-upload' => '64eeda79',
|
||||||
'phabricator-filetree-view-css' => 'cd37aa53',
|
'phabricator-filetree-view-css' => 'c98eaabf',
|
||||||
'phabricator-flag-css' => 'cd37aa53',
|
'phabricator-flag-css' => 'c98eaabf',
|
||||||
'phabricator-hovercard' => '64eeda79',
|
'phabricator-hovercard' => '64eeda79',
|
||||||
'phabricator-jump-nav' => 'cd37aa53',
|
'phabricator-jump-nav' => 'c98eaabf',
|
||||||
'phabricator-keyboard-shortcut' => '64eeda79',
|
'phabricator-keyboard-shortcut' => '64eeda79',
|
||||||
'phabricator-keyboard-shortcut-manager' => '64eeda79',
|
'phabricator-keyboard-shortcut-manager' => '64eeda79',
|
||||||
'phabricator-main-menu-view' => 'cd37aa53',
|
'phabricator-main-menu-view' => 'c98eaabf',
|
||||||
'phabricator-menu-item' => '64eeda79',
|
'phabricator-menu-item' => '64eeda79',
|
||||||
'phabricator-nav-view-css' => 'cd37aa53',
|
'phabricator-nav-view-css' => 'c98eaabf',
|
||||||
'phabricator-notification' => '64eeda79',
|
'phabricator-notification' => '64eeda79',
|
||||||
'phabricator-notification-css' => 'cd37aa53',
|
'phabricator-notification-css' => 'c98eaabf',
|
||||||
'phabricator-notification-menu-css' => 'cd37aa53',
|
'phabricator-notification-menu-css' => 'c98eaabf',
|
||||||
'phabricator-object-selector-css' => '4dc2311c',
|
'phabricator-object-selector-css' => '4dc2311c',
|
||||||
'phabricator-phtize' => '64eeda79',
|
'phabricator-phtize' => '64eeda79',
|
||||||
'phabricator-prefab' => '64eeda79',
|
'phabricator-prefab' => '64eeda79',
|
||||||
'phabricator-project-tag-css' => '49898640',
|
'phabricator-project-tag-css' => '49898640',
|
||||||
'phabricator-property-list-view-css' => 'cd37aa53',
|
'phabricator-property-list-view-css' => 'c98eaabf',
|
||||||
'phabricator-remarkup-css' => 'cd37aa53',
|
'phabricator-remarkup-css' => 'c98eaabf',
|
||||||
'phabricator-shaped-request' => '5e9e5c4e',
|
'phabricator-shaped-request' => '5e9e5c4e',
|
||||||
'phabricator-side-menu-view-css' => 'cd37aa53',
|
'phabricator-side-menu-view-css' => 'c98eaabf',
|
||||||
'phabricator-standard-page-view' => 'cd37aa53',
|
'phabricator-standard-page-view' => 'c98eaabf',
|
||||||
'phabricator-tag-view-css' => 'cd37aa53',
|
'phabricator-tag-view-css' => 'c98eaabf',
|
||||||
'phabricator-textareautils' => '64eeda79',
|
'phabricator-textareautils' => '64eeda79',
|
||||||
'phabricator-tooltip' => '64eeda79',
|
'phabricator-tooltip' => '64eeda79',
|
||||||
'phabricator-transaction-view-css' => 'cd37aa53',
|
'phabricator-transaction-view-css' => 'c98eaabf',
|
||||||
'phabricator-zindex-css' => 'cd37aa53',
|
'phabricator-zindex-css' => 'c98eaabf',
|
||||||
'phui-button-css' => 'cd37aa53',
|
'phui-button-css' => 'c98eaabf',
|
||||||
'phui-form-css' => 'cd37aa53',
|
'phui-form-css' => 'c98eaabf',
|
||||||
'phui-form-view-css' => 'cd37aa53',
|
'phui-form-view-css' => 'c98eaabf',
|
||||||
'phui-header-view-css' => 'cd37aa53',
|
'phui-header-view-css' => 'c98eaabf',
|
||||||
'phui-icon-view-css' => 'cd37aa53',
|
'phui-icon-view-css' => 'c98eaabf',
|
||||||
'phui-list-view-css' => 'cd37aa53',
|
'phui-list-view-css' => 'c98eaabf',
|
||||||
'phui-object-item-list-view-css' => 'cd37aa53',
|
'phui-object-item-list-view-css' => 'c98eaabf',
|
||||||
'phui-spacing-css' => 'cd37aa53',
|
'phui-spacing-css' => 'c98eaabf',
|
||||||
'sprite-apps-large-css' => 'cd37aa53',
|
'sprite-apps-large-css' => 'c98eaabf',
|
||||||
'sprite-gradient-css' => 'cd37aa53',
|
'sprite-gradient-css' => 'c98eaabf',
|
||||||
'sprite-icons-css' => 'cd37aa53',
|
'sprite-icons-css' => 'c98eaabf',
|
||||||
'sprite-menu-css' => 'cd37aa53',
|
'sprite-menu-css' => 'c98eaabf',
|
||||||
'sprite-status-css' => 'cd37aa53',
|
'sprite-status-css' => 'c98eaabf',
|
||||||
'syntax-highlighting-css' => 'cd37aa53',
|
'syntax-highlighting-css' => 'c98eaabf',
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
|
@ -1472,7 +1472,10 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorPolicy' => 'applications/policy/filter/PhabricatorPolicy.php',
|
'PhabricatorPolicy' => 'applications/policy/filter/PhabricatorPolicy.php',
|
||||||
'PhabricatorPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorPolicyAwareQuery.php',
|
'PhabricatorPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorPolicyAwareQuery.php',
|
||||||
'PhabricatorPolicyAwareTestQuery' => 'applications/policy/__tests__/PhabricatorPolicyAwareTestQuery.php',
|
'PhabricatorPolicyAwareTestQuery' => 'applications/policy/__tests__/PhabricatorPolicyAwareTestQuery.php',
|
||||||
'PhabricatorPolicyCapability' => 'applications/policy/constants/PhabricatorPolicyCapability.php',
|
'PhabricatorPolicyCapability' => 'applications/policy/capability/PhabricatorPolicyCapability.php',
|
||||||
|
'PhabricatorPolicyCapabilityCanEdit' => 'applications/policy/capability/PhabricatorPolicyCapabilityCanEdit.php',
|
||||||
|
'PhabricatorPolicyCapabilityCanJoin' => 'applications/policy/capability/PhabricatorPolicyCapabilityCanJoin.php',
|
||||||
|
'PhabricatorPolicyCapabilityCanView' => 'applications/policy/capability/PhabricatorPolicyCapabilityCanView.php',
|
||||||
'PhabricatorPolicyConfigOptions' => 'applications/policy/config/PhabricatorPolicyConfigOptions.php',
|
'PhabricatorPolicyConfigOptions' => 'applications/policy/config/PhabricatorPolicyConfigOptions.php',
|
||||||
'PhabricatorPolicyConstants' => 'applications/policy/constants/PhabricatorPolicyConstants.php',
|
'PhabricatorPolicyConstants' => 'applications/policy/constants/PhabricatorPolicyConstants.php',
|
||||||
'PhabricatorPolicyController' => 'applications/policy/controller/PhabricatorPolicyController.php',
|
'PhabricatorPolicyController' => 'applications/policy/controller/PhabricatorPolicyController.php',
|
||||||
|
@ -3656,7 +3659,10 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorPolicies' => 'PhabricatorPolicyConstants',
|
'PhabricatorPolicies' => 'PhabricatorPolicyConstants',
|
||||||
'PhabricatorPolicyAwareQuery' => 'PhabricatorOffsetPagedQuery',
|
'PhabricatorPolicyAwareQuery' => 'PhabricatorOffsetPagedQuery',
|
||||||
'PhabricatorPolicyAwareTestQuery' => 'PhabricatorPolicyAwareQuery',
|
'PhabricatorPolicyAwareTestQuery' => 'PhabricatorPolicyAwareQuery',
|
||||||
'PhabricatorPolicyCapability' => 'PhabricatorPolicyConstants',
|
'PhabricatorPolicyCapability' => 'Phobject',
|
||||||
|
'PhabricatorPolicyCapabilityCanEdit' => 'PhabricatorPolicyCapability',
|
||||||
|
'PhabricatorPolicyCapabilityCanJoin' => 'PhabricatorPolicyCapability',
|
||||||
|
'PhabricatorPolicyCapabilityCanView' => 'PhabricatorPolicyCapability',
|
||||||
'PhabricatorPolicyConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
'PhabricatorPolicyConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||||
'PhabricatorPolicyController' => 'PhabricatorController',
|
'PhabricatorPolicyController' => 'PhabricatorController',
|
||||||
'PhabricatorPolicyDataTestCase' => 'PhabricatorTestCase',
|
'PhabricatorPolicyDataTestCase' => 'PhabricatorTestCase',
|
||||||
|
|
|
@ -172,22 +172,25 @@ class AphrontDefaultApplicationConfiguration
|
||||||
$list = phutil_tag('ul', array(), $list);
|
$list = phutil_tag('ul', array(), $list);
|
||||||
}
|
}
|
||||||
|
|
||||||
$content = phutil_tag(
|
$content = array(
|
||||||
'div',
|
phutil_tag(
|
||||||
array(
|
'div',
|
||||||
'class' => 'aphront-policy-exception',
|
array(
|
||||||
),
|
'class' => 'aphront-policy-rejection',
|
||||||
array(
|
),
|
||||||
$ex->getMessage(),
|
$ex->getRejection()),
|
||||||
$list,
|
phutil_tag(
|
||||||
));
|
'div',
|
||||||
|
array(
|
||||||
|
'class' => 'aphront-capability-details',
|
||||||
|
),
|
||||||
|
pht('Users with the "%s" capability:', $ex->getCapabilityName())),
|
||||||
|
$list,
|
||||||
|
);
|
||||||
|
|
||||||
$dialog = new AphrontDialogView();
|
$dialog = new AphrontDialogView();
|
||||||
$dialog
|
$dialog
|
||||||
->setTitle(
|
->setTitle($ex->getTitle())
|
||||||
$is_serious
|
|
||||||
? 'Access Denied'
|
|
||||||
: "You Shall Not Pass")
|
|
||||||
->setClass('aphront-access-dialog')
|
->setClass('aphront-access-dialog')
|
||||||
->setUser($user)
|
->setUser($user)
|
||||||
->appendChild($content);
|
->appendChild($content);
|
||||||
|
|
|
@ -357,11 +357,9 @@ final class DifferentialRevision extends DifferentialDAO
|
||||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
case PhabricatorPolicyCapability::CAN_VIEW:
|
||||||
$description[] = pht(
|
$description[] = pht(
|
||||||
"A revision's reviewers can always view it.");
|
"A revision's reviewers can always view it.");
|
||||||
if ($this->getRepositoryPHID()) {
|
$description[] = pht(
|
||||||
$description[] = pht(
|
'If a revision belongs to a repository, other users must be able '.
|
||||||
'This revision belongs to a repository. Other users must be able '.
|
'to view the repository in order to view the revision.');
|
||||||
'to view the repository in order to view this revision.');
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
abstract class PhabricatorPolicyCapability extends Phobject {
|
||||||
|
|
||||||
|
const CAN_VIEW = 'view';
|
||||||
|
const CAN_EDIT = 'edit';
|
||||||
|
const CAN_JOIN = 'join';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the unique key identifying this capability. This key must be globally
|
||||||
|
* unique. Application capabilities should be namespaced. For example:
|
||||||
|
*
|
||||||
|
* application.create
|
||||||
|
*
|
||||||
|
* @return string Globally unique capability key.
|
||||||
|
*/
|
||||||
|
abstract public function getCapabilityKey();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a human-readable descriptive name for this capability, like
|
||||||
|
* "Can View".
|
||||||
|
*
|
||||||
|
* @return string Human-readable name describing the capability.
|
||||||
|
*/
|
||||||
|
abstract public function getCapabilityName();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a human-readable string describing what not having this capability
|
||||||
|
* prevents the user from doing. For example:
|
||||||
|
*
|
||||||
|
* - You do not have permission to edit this object.
|
||||||
|
* - You do not have permission to create new tasks.
|
||||||
|
*
|
||||||
|
* @return string Human-readable name describing what failing a check for this
|
||||||
|
* capability prevents the user from doing.
|
||||||
|
*/
|
||||||
|
abstract public function describeCapabilityRejection();
|
||||||
|
|
||||||
|
|
||||||
|
final public static function getCapabilityByKey($key) {
|
||||||
|
return idx(self::getCapabilityMap(), $key);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public static function getCapabilityMap() {
|
||||||
|
static $map;
|
||||||
|
if ($map === null) {
|
||||||
|
$capabilities = id(new PhutilSymbolLoader())
|
||||||
|
->setAncestorClass(__CLASS__)
|
||||||
|
->loadObjects();
|
||||||
|
|
||||||
|
$map = mpull($capabilities, null, 'getCapabilityKey');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $map;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorPolicyCapabilityCanEdit
|
||||||
|
extends PhabricatorPolicyCapability {
|
||||||
|
|
||||||
|
public function getCapabilityKey() {
|
||||||
|
return self::CAN_EDIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCapabilityName() {
|
||||||
|
return pht('Can Edit');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function describeCapabilityRejection() {
|
||||||
|
return pht('You do not have permission to edit this object.');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorPolicyCapabilityCanJoin
|
||||||
|
extends PhabricatorPolicyCapability {
|
||||||
|
|
||||||
|
public function getCapabilityKey() {
|
||||||
|
return self::CAN_JOIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCapabilityName() {
|
||||||
|
return pht('Can Join');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function describeCapabilityRejection() {
|
||||||
|
return pht('You do not have permission to join this object.');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorPolicyCapabilityCanView
|
||||||
|
extends PhabricatorPolicyCapability {
|
||||||
|
|
||||||
|
public function getCapabilityKey() {
|
||||||
|
return self::CAN_VIEW;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCapabilityName() {
|
||||||
|
return pht('Can View');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function describeCapabilityRejection() {
|
||||||
|
return pht('You do not have permission to view this object.');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,9 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
final class PhabricatorPolicyCapability extends PhabricatorPolicyConstants {
|
|
||||||
|
|
||||||
const CAN_VIEW = 'view';
|
|
||||||
const CAN_EDIT = 'edit';
|
|
||||||
const CAN_JOIN = 'join';
|
|
||||||
|
|
||||||
}
|
|
|
@ -45,9 +45,17 @@ final class PhabricatorPolicyExplainController
|
||||||
->executeOne();
|
->executeOne();
|
||||||
$object_uri = $handle->getURI();
|
$object_uri = $handle->getURI();
|
||||||
|
|
||||||
$explanation = $policy->getExplanation($capability);
|
$explanation = PhabricatorPolicy::getPolicyExplanation(
|
||||||
|
$viewer,
|
||||||
|
$policy->getPHID());
|
||||||
|
|
||||||
$auto_info = (array)$object->describeAutomaticCapability($capability);
|
$auto_info = (array)$object->describeAutomaticCapability($capability);
|
||||||
|
|
||||||
|
$auto_info = array_merge(
|
||||||
|
array($explanation),
|
||||||
|
$auto_info);
|
||||||
|
$auto_info = array_filter($auto_info);
|
||||||
|
|
||||||
foreach ($auto_info as $key => $info) {
|
foreach ($auto_info as $key => $info) {
|
||||||
$auto_info[$key] = phutil_tag('li', array(), $info);
|
$auto_info[$key] = phutil_tag('li', array(), $info);
|
||||||
}
|
}
|
||||||
|
@ -56,14 +64,19 @@ final class PhabricatorPolicyExplainController
|
||||||
}
|
}
|
||||||
|
|
||||||
$content = array(
|
$content = array(
|
||||||
$explanation,
|
pht('Users with the "%s" capability:', "Can View"),
|
||||||
$auto_info,
|
$auto_info,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$object_name = pht(
|
||||||
|
'%s %s',
|
||||||
|
$handle->getTypeName(),
|
||||||
|
$handle->getObjectName());
|
||||||
|
|
||||||
$dialog = id(new AphrontDialogView())
|
$dialog = id(new AphrontDialogView())
|
||||||
->setUser($viewer)
|
->setUser($viewer)
|
||||||
->setClass('aphront-access-dialog')
|
->setClass('aphront-access-dialog')
|
||||||
->setTitle(pht('Policy Details'))
|
->setTitle(pht('Policy Details: %s', $object_name))
|
||||||
->appendChild($content)
|
->appendChild($content)
|
||||||
->addCancelButton($object_uri, pht('Done'));
|
->addCancelButton($object_uri, pht('Done'));
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,38 @@
|
||||||
|
|
||||||
final class PhabricatorPolicyException extends Exception {
|
final class PhabricatorPolicyException extends Exception {
|
||||||
|
|
||||||
|
private $title;
|
||||||
|
private $rejection;
|
||||||
|
private $capabilityName;
|
||||||
private $moreInfo = array();
|
private $moreInfo = array();
|
||||||
|
|
||||||
|
public function setTitle($title) {
|
||||||
|
$this->title = $title;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle() {
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCapabilityName($capability_name) {
|
||||||
|
$this->capabilityName = $capability_name;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCapabilityName() {
|
||||||
|
return $this->capabilityName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRejection($rejection) {
|
||||||
|
$this->rejection = $rejection;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRejection() {
|
||||||
|
return $this->rejection;
|
||||||
|
}
|
||||||
|
|
||||||
public function setMoreInfo(array $more_info) {
|
public function setMoreInfo(array $more_info) {
|
||||||
$this->moreInfo = $more_info;
|
$this->moreInfo = $more_info;
|
||||||
return $this;
|
return $this;
|
||||||
|
|
|
@ -130,52 +130,40 @@ final class PhabricatorPolicy {
|
||||||
return $this->getName();
|
return $this->getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getExplanation($capability) {
|
public static function getPolicyExplanation(
|
||||||
switch ($capability) {
|
PhabricatorUser $viewer,
|
||||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
$policy) {
|
||||||
switch ($this->getPHID()) {
|
|
||||||
case PhabricatorPolicies::POLICY_PUBLIC:
|
|
||||||
return pht('Visible to the entire internet.');
|
|
||||||
case PhabricatorPolicies::POLICY_USER:
|
|
||||||
return pht('Visible to all logged in users.');
|
|
||||||
case PhabricatorPolicies::POLICY_ADMIN:
|
|
||||||
return pht('Visible to all administrators.');
|
|
||||||
case PhabricatorPolicies::POLICY_NOONE:
|
|
||||||
return pht('Not visible to anyone by default.');
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ($this->getType()) {
|
switch ($policy) {
|
||||||
case PhabricatorPolicyType::TYPE_PROJECT:
|
case PhabricatorPolicies::POLICY_PUBLIC:
|
||||||
return pht(
|
return pht('This object is public.');
|
||||||
'Visible to members of the project "%s".',
|
case PhabricatorPolicies::POLICY_USER:
|
||||||
$this->getName());
|
return pht('Logged in users can take this action.');
|
||||||
case PhabricatorPolicyType::TYPE_MASKED:
|
case PhabricatorPolicies::POLICY_ADMIN:
|
||||||
return pht('Other: %s', $this->getName());
|
return pht('Administrators can take this action.');
|
||||||
}
|
case PhabricatorPolicies::POLICY_NOONE:
|
||||||
break;
|
return pht('By default, no one can take this action.');
|
||||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
default:
|
||||||
switch ($this->getPHID()) {
|
$handle = id(new PhabricatorHandleQuery())
|
||||||
case PhabricatorPolicies::POLICY_USER:
|
->setViewer($viewer)
|
||||||
return pht('Editable by all logged in users.');
|
->withPHIDs(array($policy))
|
||||||
case PhabricatorPolicies::POLICY_ADMIN:
|
->executeOne();
|
||||||
return pht('Editable by all administrators.');
|
|
||||||
case PhabricatorPolicies::POLICY_NOONE:
|
|
||||||
return pht('Not editable by default.');
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ($this->getType()) {
|
$type = phid_get_type($policy);
|
||||||
case PhabricatorPolicyType::TYPE_PROJECT:
|
if ($type == PhabricatorProjectPHIDTypeProject::TYPECONST) {
|
||||||
return pht(
|
return pht(
|
||||||
'Editable by members of the project "%s".',
|
'Members of the project "%s" can take this action.',
|
||||||
$this->getName());
|
$handle->getFullName());
|
||||||
case PhabricatorPolicyType::TYPE_MASKED:
|
} else if ($type == PhabricatorPeoplePHIDTypeUser::TYPECONST) {
|
||||||
return pht('Other: %s', $this->getName());
|
return pht(
|
||||||
|
'%s can take this action.',
|
||||||
|
$handle->getFullName());
|
||||||
|
} else {
|
||||||
|
return pht(
|
||||||
|
'This object has an unknown or invalid policy setting ("%s").',
|
||||||
|
$policy);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return pht('?');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFullName() {
|
public function getFullName() {
|
||||||
|
|
|
@ -242,118 +242,68 @@ final class PhabricatorPolicyFilter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$more = array();
|
$capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
|
||||||
switch ($capability) {
|
if ($capobj) {
|
||||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
$rejection = $capobj->describeCapabilityRejection();
|
||||||
$message = pht(
|
$capability_name = $capobj->getCapabilityName();
|
||||||
'This object exists, but you do not have permission to view it.');
|
} else {
|
||||||
break;
|
$rejection = pht(
|
||||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
'You do not have the required capability ("%s") to do whatever you '.
|
||||||
$message = pht('You do not have permission to edit this object.');
|
'are trying to do.',
|
||||||
break;
|
$capability);
|
||||||
case PhabricatorPolicyCapability::CAN_JOIN:
|
$capability_name = $capability;
|
||||||
$message = pht('You do not have permission to join this object.');
|
|
||||||
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) {
|
$more = PhabricatorPolicy::getPolicyExplanation($this->viewer, $policy);
|
||||||
case PhabricatorPolicies::POLICY_PUBLIC:
|
$exceptions = $object->describeAutomaticCapability($capability);
|
||||||
// Presumably, this is a bug, so we don't bother specializing the
|
|
||||||
// strings.
|
|
||||||
$more = pht('This object is public.');
|
|
||||||
break;
|
|
||||||
case PhabricatorPolicies::POLICY_USER:
|
|
||||||
// We always raise this as "log in", so we don't need to specialize.
|
|
||||||
$more = pht('This object is available to logged in users.');
|
|
||||||
break;
|
|
||||||
case PhabricatorPolicies::POLICY_ADMIN:
|
|
||||||
switch ($capability) {
|
|
||||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
|
||||||
$more = pht('Administrators can view this object.');
|
|
||||||
break;
|
|
||||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
|
||||||
$more = pht('Administrators can edit this object.');
|
|
||||||
break;
|
|
||||||
case PhabricatorPolicyCapability::CAN_JOIN:
|
|
||||||
$more = pht('Administrators can join this object.');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PhabricatorPolicies::POLICY_NOONE:
|
|
||||||
switch ($capability) {
|
|
||||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
|
||||||
$more = pht('By default, no one can view this object.');
|
|
||||||
break;
|
|
||||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
|
||||||
$more = pht('By default, no one can edit this object.');
|
|
||||||
break;
|
|
||||||
case PhabricatorPolicyCapability::CAN_JOIN:
|
|
||||||
$more = pht('By default, no one can join this object.');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$handle = id(new PhabricatorHandleQuery())
|
|
||||||
->setViewer($this->viewer)
|
|
||||||
->withPHIDs(array($policy))
|
|
||||||
->executeOne();
|
|
||||||
|
|
||||||
$type = phid_get_type($policy);
|
$details = array_filter(array_merge(array($more), (array)$exceptions));
|
||||||
if ($type == PhabricatorProjectPHIDTypeProject::TYPECONST) {
|
|
||||||
switch ($capability) {
|
// NOTE: Not every policy object has a PHID, just pull an arbitrary
|
||||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
// "unknown object" handle if this fails. We're just using this to provide
|
||||||
$more = pht(
|
// a better error message if we can.
|
||||||
'This object is visible to members of the project "%s".',
|
|
||||||
$handle->getFullName());
|
$phid = '?';
|
||||||
break;
|
if ($object instanceof PhabricatorLiskDAO) {
|
||||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
try {
|
||||||
$more = pht(
|
$phid = $object->getPHID();
|
||||||
'This object can be edited by members of the project "%s".',
|
} catch (Exception $ignored) {
|
||||||
$handle->getFullName());
|
// Ignore.
|
||||||
break;
|
}
|
||||||
case PhabricatorPolicyCapability::CAN_JOIN:
|
|
||||||
$more = pht(
|
|
||||||
'This object can be joined by members of the project "%s".',
|
|
||||||
$handle->getFullName());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if ($type == PhabricatorPeoplePHIDTypeUser::TYPECONST) {
|
|
||||||
switch ($capability) {
|
|
||||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
|
||||||
$more = pht(
|
|
||||||
'%s can view this object.',
|
|
||||||
$handle->getFullName());
|
|
||||||
break;
|
|
||||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
|
||||||
$more = pht(
|
|
||||||
'%s can edit this object.',
|
|
||||||
$handle->getFullName());
|
|
||||||
break;
|
|
||||||
case PhabricatorPolicyCapability::CAN_JOIN:
|
|
||||||
$more = pht(
|
|
||||||
'%s can join this object.',
|
|
||||||
$handle->getFullName());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$more = pht("This object has an unknown or invalid policy setting.");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$more = array_merge(
|
$handle = id(new PhabricatorHandleQuery())
|
||||||
array_filter(array($more)),
|
->setViewer($this->viewer)
|
||||||
array_filter((array)$object->describeAutomaticCapability($capability)));
|
->withPHIDs(array($phid))
|
||||||
|
->executeOne();
|
||||||
|
$object_name = pht(
|
||||||
|
'%s %s',
|
||||||
|
$handle->getTypeName(),
|
||||||
|
$handle->getObjectName());
|
||||||
|
|
||||||
$exception = new PhabricatorPolicyException($message);
|
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
|
||||||
$exception->setMoreInfo($more);
|
if ($is_serious) {
|
||||||
|
$title = pht(
|
||||||
|
'Access Denied: %s',
|
||||||
|
$object_name);
|
||||||
|
} else {
|
||||||
|
$title = pht(
|
||||||
|
'You Shall Not Pass: %s',
|
||||||
|
$object_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
$full_message = pht(
|
||||||
|
'[%s] (%s) %s // %s',
|
||||||
|
$title,
|
||||||
|
$capability_name,
|
||||||
|
$rejection,
|
||||||
|
implode(' ', $details));
|
||||||
|
|
||||||
|
$exception = id(new PhabricatorPolicyException($full_message))
|
||||||
|
->setTitle($title)
|
||||||
|
->setRejection($rejection)
|
||||||
|
->setCapabilityName($capability_name)
|
||||||
|
->setMoreInfo($details);
|
||||||
|
|
||||||
throw $exception;
|
throw $exception;
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,8 @@ final class PhabricatorPolicyManagementShowWorkflow
|
||||||
foreach ($policies as $capability => $policy) {
|
foreach ($policies as $capability => $policy) {
|
||||||
$console->writeOut(" **%s**\n", $capability);
|
$console->writeOut(" **%s**\n", $capability);
|
||||||
$console->writeOut(" %s\n", $policy->renderDescription());
|
$console->writeOut(" %s\n", $policy->renderDescription());
|
||||||
$console->writeOut(" %s\n", $policy->getExplanation($capability));
|
$console->writeOut(" %s\n",
|
||||||
|
PhabricatorPolicy::getPolicyExplanation($viewer, $policy->getPHID()));
|
||||||
$console->writeOut("\n");
|
$console->writeOut("\n");
|
||||||
|
|
||||||
$more = (array)$object->describeAutomaticCapability($capability);
|
$more = (array)$object->describeAutomaticCapability($capability);
|
||||||
|
|
|
@ -116,3 +116,11 @@
|
||||||
margin: 12px 24px;
|
margin: 12px 24px;
|
||||||
list-style: circle;
|
list-style: circle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.aphront-policy-rejection {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aphront-capability-details {
|
||||||
|
margin: 20px 0 4px;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue