mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-29 00:40:57 +01:00
Add basic per-object privacy policies
Summary: Provides a basic start for access policies. Objects expose various capabilities, like CAN_VIEW, CAN_EDIT, etc., and set a policy for each capability. We currently implement three policies, PUBLIC (anyone, including logged-out), USERS (any logged-in) and NOONE (nobody). There's also a way to provide automatic capability grants (e.g., the owner of an object can always see it, even if some capability is set to "NOONE"), but I'm not sure how great the implementation feels and it might change. Most of the code here is providing a primitive for efficient policy-aware list queries. The problem with doing queries naively is that you have to do crazy amounts of filtering, e.g. to show the user page 6, you need to filter at least 600 objects (and likely more) before you can figure out which ones are 500-600 for them. You can't just do "LIMIT 500, 100" because that might have only 50 results, or no results. Instead, the query looks like "WHERE id > last_visible_id", and then we fetch additional pages as necessary to satisfy the request. The general idea is that we move all data access to Query classes and have them do object filtering. The ID paging primitive allows efficient paging in most cases, and the executeOne() method provides a concise way to do policy checks for edit/view screens. We'll probably end up with mostly broader policy UIs or configuration-based policies, but there are at least a few cases for per-object privacy (e.g., marking tasks as "Security", and restricting things to the members of projects) so I figured we'd start with a flexible primitive and the simplify it in the UI where we can. Test Plan: Unit tests, played around in the UI with various policy settings. Reviewers: btrahan, vrana, jungejason Reviewed By: btrahan CC: aran Maniphest Tasks: T603 Differential Revision: https://secure.phabricator.com/D2210
This commit is contained in:
parent
6be9f6f3a8
commit
ded641ae32
36 changed files with 1297 additions and 134 deletions
|
@ -39,6 +39,7 @@ phutil_register_library_map(array(
|
|||
'AphrontFormLayoutView' => 'view/form/layout',
|
||||
'AphrontFormMarkupControl' => 'view/form/control/markup',
|
||||
'AphrontFormPasswordControl' => 'view/form/control/password',
|
||||
'AphrontFormPolicyControl' => 'view/form/control/policy',
|
||||
'AphrontFormRadioButtonControl' => 'view/form/control/radio',
|
||||
'AphrontFormRecaptchaControl' => 'view/form/control/recaptcha',
|
||||
'AphrontFormSelectControl' => 'view/form/control/select',
|
||||
|
@ -54,6 +55,7 @@ phutil_register_library_map(array(
|
|||
'AphrontHeadsupActionListView' => 'view/layout/headsup/actionlist',
|
||||
'AphrontHeadsupActionView' => 'view/layout/headsup/action',
|
||||
'AphrontHeadsupView' => 'view/layout/headsup/panel',
|
||||
'AphrontIDPagerView' => 'view/control/idpager',
|
||||
'AphrontIsolatedDatabaseConnection' => 'storage/connection/isolated',
|
||||
'AphrontIsolatedDatabaseConnectionTestCase' => 'storage/connection/isolated/__tests__',
|
||||
'AphrontIsolatedHTTPSink' => 'aphront/sink/test',
|
||||
|
@ -633,6 +635,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorHash' => 'infrastructure/util/hash',
|
||||
'PhabricatorHelpController' => 'applications/help/controller/base',
|
||||
'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/keyboardshortcut',
|
||||
'PhabricatorIDPagedPolicyQuery' => 'infrastructure/query/policy/idpaged',
|
||||
'PhabricatorIRCBot' => 'infrastructure/daemon/irc/bot',
|
||||
'PhabricatorIRCDifferentialNotificationHandler' => 'infrastructure/daemon/irc/handler/differentialnotification',
|
||||
'PhabricatorIRCHandler' => 'infrastructure/daemon/irc/handler/base',
|
||||
|
@ -745,12 +748,23 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPasteController' => 'applications/paste/controller/base',
|
||||
'PhabricatorPasteDAO' => 'applications/paste/storage/base',
|
||||
'PhabricatorPasteListController' => 'applications/paste/controller/list',
|
||||
'PhabricatorPasteQuery' => 'applications/paste/query/paste',
|
||||
'PhabricatorPasteViewController' => 'applications/paste/controller/view',
|
||||
'PhabricatorPeopleController' => 'applications/people/controller/base',
|
||||
'PhabricatorPeopleEditController' => 'applications/people/controller/edit',
|
||||
'PhabricatorPeopleListController' => 'applications/people/controller/list',
|
||||
'PhabricatorPeopleLogsController' => 'applications/people/controller/logs',
|
||||
'PhabricatorPeopleProfileController' => 'applications/people/controller/profile',
|
||||
'PhabricatorPolicies' => 'applications/policy/constants/policy',
|
||||
'PhabricatorPolicyCapability' => 'applications/policy/constants/capability',
|
||||
'PhabricatorPolicyConstants' => 'applications/policy/constants/base',
|
||||
'PhabricatorPolicyException' => 'applications/policy/exception/base',
|
||||
'PhabricatorPolicyFilter' => 'applications/policy/filter/policy',
|
||||
'PhabricatorPolicyInterface' => 'applications/policy/interface/policy',
|
||||
'PhabricatorPolicyQuery' => 'infrastructure/query/policy/base',
|
||||
'PhabricatorPolicyTestCase' => 'applications/policy/__tests__',
|
||||
'PhabricatorPolicyTestObject' => 'applications/policy/__tests__',
|
||||
'PhabricatorPolicyTestQuery' => 'applications/policy/__tests__',
|
||||
'PhabricatorProfileHeaderView' => 'view/layout/profileheader',
|
||||
'PhabricatorProject' => 'applications/project/storage/project',
|
||||
'PhabricatorProjectAffiliation' => 'applications/project/storage/affiliation',
|
||||
|
@ -1023,6 +1037,7 @@ phutil_register_library_map(array(
|
|||
'AphrontFormLayoutView' => 'AphrontView',
|
||||
'AphrontFormMarkupControl' => 'AphrontFormControl',
|
||||
'AphrontFormPasswordControl' => 'AphrontFormControl',
|
||||
'AphrontFormPolicyControl' => 'AphrontFormControl',
|
||||
'AphrontFormRadioButtonControl' => 'AphrontFormControl',
|
||||
'AphrontFormRecaptchaControl' => 'AphrontFormControl',
|
||||
'AphrontFormSelectControl' => 'AphrontFormControl',
|
||||
|
@ -1037,6 +1052,7 @@ phutil_register_library_map(array(
|
|||
'AphrontHeadsupActionListView' => 'AphrontView',
|
||||
'AphrontHeadsupActionView' => 'AphrontView',
|
||||
'AphrontHeadsupView' => 'AphrontView',
|
||||
'AphrontIDPagerView' => 'AphrontView',
|
||||
'AphrontIsolatedDatabaseConnection' => 'AphrontDatabaseConnection',
|
||||
'AphrontIsolatedDatabaseConnectionTestCase' => 'PhabricatorTestCase',
|
||||
'AphrontIsolatedHTTPSink' => 'AphrontHTTPSink',
|
||||
|
@ -1502,6 +1518,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorGoodForNothingWorker' => 'PhabricatorWorker',
|
||||
'PhabricatorHelpController' => 'PhabricatorController',
|
||||
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
|
||||
'PhabricatorIDPagedPolicyQuery' => 'PhabricatorPolicyQuery',
|
||||
'PhabricatorIRCBot' => 'PhabricatorDaemon',
|
||||
'PhabricatorIRCDifferentialNotificationHandler' => 'PhabricatorIRCHandler',
|
||||
'PhabricatorIRCLogHandler' => 'PhabricatorIRCHandler',
|
||||
|
@ -1594,12 +1611,18 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPasteController' => 'PhabricatorController',
|
||||
'PhabricatorPasteDAO' => 'PhabricatorLiskDAO',
|
||||
'PhabricatorPasteListController' => 'PhabricatorPasteController',
|
||||
'PhabricatorPasteQuery' => 'PhabricatorIDPagedPolicyQuery',
|
||||
'PhabricatorPasteViewController' => 'PhabricatorPasteController',
|
||||
'PhabricatorPeopleController' => 'PhabricatorController',
|
||||
'PhabricatorPeopleEditController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPeopleListController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPeopleLogsController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPolicies' => 'PhabricatorPolicyConstants',
|
||||
'PhabricatorPolicyCapability' => 'PhabricatorPolicyConstants',
|
||||
'PhabricatorPolicyQuery' => 'PhabricatorQuery',
|
||||
'PhabricatorPolicyTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorPolicyTestQuery' => 'PhabricatorPolicyQuery',
|
||||
'PhabricatorProfileHeaderView' => 'AphrontView',
|
||||
'PhabricatorProject' => 'PhabricatorProjectDAO',
|
||||
'PhabricatorProjectAffiliation' => 'PhabricatorProjectDAO',
|
||||
|
@ -1804,5 +1827,13 @@ phutil_register_library_map(array(
|
|||
array(
|
||||
0 => 'PhabricatorInlineCommentInterface',
|
||||
),
|
||||
'PhabricatorPaste' =>
|
||||
array(
|
||||
0 => 'PhabricatorPolicyInterface',
|
||||
),
|
||||
'PhabricatorPolicyTestObject' =>
|
||||
array(
|
||||
0 => 'PhabricatorPolicyInterface',
|
||||
),
|
||||
),
|
||||
));
|
||||
|
|
|
@ -468,11 +468,7 @@ class AphrontDefaultApplicationConfiguration
|
|||
|
||||
public function handleException(Exception $ex) {
|
||||
|
||||
// Always log the unhandled exception.
|
||||
phlog($ex);
|
||||
|
||||
$class = phutil_escape_html(get_class($ex));
|
||||
$message = phutil_escape_html($ex->getMessage());
|
||||
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
|
||||
|
||||
$user = $this->getRequest()->getUser();
|
||||
if (!$user) {
|
||||
|
@ -480,6 +476,39 @@ class AphrontDefaultApplicationConfiguration
|
|||
$user = new PhabricatorUser();
|
||||
}
|
||||
|
||||
if ($ex instanceof PhabricatorPolicyException) {
|
||||
$content =
|
||||
'<div class="aphront-policy-exception">'.
|
||||
phutil_escape_html($ex->getMessage()).
|
||||
'</div>';
|
||||
|
||||
$dialog = new AphrontDialogView();
|
||||
$dialog
|
||||
->setTitle(
|
||||
$is_serious
|
||||
? 'Access Denied'
|
||||
: "You Shall Not Pass")
|
||||
->setClass('aphront-access-dialog')
|
||||
->setUser($user)
|
||||
->appendChild($content);
|
||||
|
||||
if ($this->getRequest()->isAjax()) {
|
||||
$dialog->addCancelButton('/', 'Close');
|
||||
} else {
|
||||
$dialog->addCancelButton('/', $is_serious ? 'OK' : 'Away With Thee');
|
||||
}
|
||||
|
||||
$response = new AphrontDialogResponse();
|
||||
$response->setDialog($dialog);
|
||||
return $response;
|
||||
}
|
||||
|
||||
// Always log the unhandled exception.
|
||||
phlog($ex);
|
||||
|
||||
$class = phutil_escape_html(get_class($ex));
|
||||
$message = phutil_escape_html($ex->getMessage());
|
||||
|
||||
if (PhabricatorEnv::getEnvConfig('phabricator.show-stack-traces')) {
|
||||
$trace = $this->renderStackTrace($ex->getTrace(), $user);
|
||||
} else {
|
||||
|
|
|
@ -24,10 +24,6 @@ final class PhabricatorPasteListController extends PhabricatorPasteController {
|
|||
private $paste;
|
||||
private $pasteText;
|
||||
|
||||
private $offset;
|
||||
private $pageSize;
|
||||
private $author;
|
||||
|
||||
private function setFilter($filter) {
|
||||
$this->filter = $filter;
|
||||
return $this;
|
||||
|
@ -40,6 +36,7 @@ final class PhabricatorPasteListController extends PhabricatorPasteController {
|
|||
$this->errorView = $error_view;
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function getErrorView() {
|
||||
return $this->errorView;
|
||||
}
|
||||
|
@ -68,40 +65,19 @@ final class PhabricatorPasteListController extends PhabricatorPasteController {
|
|||
return $this->pasteText;
|
||||
}
|
||||
|
||||
private function setOffset($offset) {
|
||||
$this->offset = $offset;
|
||||
return $this;
|
||||
}
|
||||
private function getOffset() {
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
private function setPageSize($page_size) {
|
||||
$this->pageSize = $page_size;
|
||||
return $this;
|
||||
}
|
||||
private function getPageSize() {
|
||||
return $this->pageSize;
|
||||
}
|
||||
|
||||
private function setAuthor($author) {
|
||||
$this->author = $author;
|
||||
return $this;
|
||||
}
|
||||
private function getAuthor() {
|
||||
return $this->author;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->setFilter(idx($data, 'filter', 'create'));
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
$paste_list = array();
|
||||
$pager = null;
|
||||
|
||||
$pager = new AphrontIDPagerView();
|
||||
$pager->readFromRequest($request);
|
||||
|
||||
$query = new PhabricatorPasteQuery();
|
||||
$query->setViewer($user);
|
||||
|
||||
switch ($this->getFilter()) {
|
||||
case 'create':
|
||||
|
@ -111,22 +87,18 @@ final class PhabricatorPasteListController extends PhabricatorPasteController {
|
|||
if ($created_paste_redirect) {
|
||||
return $created_paste_redirect;
|
||||
}
|
||||
// if we didn't succeed or we weren't trying, load just a few
|
||||
// recent pastes with NO pagination
|
||||
$this->setOffset(0);
|
||||
$this->setPageSize(10);
|
||||
list($paste_list, $pager) = $this->loadPasteList();
|
||||
break;
|
||||
|
||||
$query->setLimit(10);
|
||||
$paste_list = $query->execute();
|
||||
|
||||
$pager = null;
|
||||
break;
|
||||
case 'my':
|
||||
$this->setAuthor($user->getPHID());
|
||||
$this->setOffset($request->getInt('page', 0));
|
||||
list($paste_list, $pager) = $this->loadPasteList();
|
||||
$query->withAuthorPHIDs(array($user->getPHID()));
|
||||
$paste_list = $query->executeWithPager($pager);
|
||||
break;
|
||||
|
||||
case 'all':
|
||||
$this->setOffset($request->getInt('page', 0));
|
||||
list($paste_list, $pager) = $this->loadPasteList();
|
||||
$paste_list = $query->executeWithPager($pager);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -171,24 +143,19 @@ final class PhabricatorPasteListController extends PhabricatorPasteController {
|
|||
),
|
||||
'See all Pastes');
|
||||
$header = "Recent Pastes · {$see_all}";
|
||||
$side_nav->appendChild($this->renderPasteList($paste_list,
|
||||
$header,
|
||||
$pager = null));
|
||||
break;
|
||||
case 'my':
|
||||
$header = 'Your Pastes';
|
||||
$side_nav->appendChild($this->renderPasteList($paste_list,
|
||||
$header,
|
||||
$pager));
|
||||
break;
|
||||
case 'all':
|
||||
$header = 'All Pastes';
|
||||
$side_nav->appendChild($this->renderPasteList($paste_list,
|
||||
$header,
|
||||
$pager));
|
||||
break;
|
||||
}
|
||||
|
||||
$side_nav->appendChild(
|
||||
$this->renderPasteList($paste_list, $header, $pager));
|
||||
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
$side_nav,
|
||||
array(
|
||||
|
@ -282,30 +249,66 @@ final class PhabricatorPasteListController extends PhabricatorPasteController {
|
|||
$this->setPaste($new_paste);
|
||||
}
|
||||
|
||||
private function loadPasteList() {
|
||||
private function renderCreatePaste() {
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
$pager = new AphrontPagerView();
|
||||
$pager->setOffset($this->getOffset());
|
||||
if ($this->getPageSize()) {
|
||||
$pager->setPageSize($this->getPageSize());
|
||||
}
|
||||
$new_paste = $this->getPaste();
|
||||
|
||||
if ($this->getAuthor()) {
|
||||
$pastes = id(new PhabricatorPaste())->loadAllWhere(
|
||||
'authorPHID = %s ORDER BY id DESC LIMIT %d, %d',
|
||||
$this->getAuthor(),
|
||||
$pager->getOffset(),
|
||||
$pager->getPageSize() + 1);
|
||||
} else {
|
||||
$pastes = id(new PhabricatorPaste())->loadAllWhere(
|
||||
'1 = 1 ORDER BY id DESC LIMIT %d, %d',
|
||||
$pager->getOffset(),
|
||||
$pager->getPageSize() + 1);
|
||||
}
|
||||
$form = new AphrontFormView();
|
||||
|
||||
$pastes = $pager->sliceResults($pastes);
|
||||
$pager->setURI($request->getRequestURI(), 'page');
|
||||
$available_languages = PhabricatorEnv::getEnvConfig(
|
||||
'pygments.dropdown-choices');
|
||||
asort($available_languages);
|
||||
$language_select = id(new AphrontFormSelectControl())
|
||||
->setLabel('Language')
|
||||
->setName('language')
|
||||
->setValue($new_paste->getLanguage())
|
||||
->setOptions($available_languages);
|
||||
|
||||
$form
|
||||
->setUser($user)
|
||||
->setAction($request->getRequestURI()->getPath())
|
||||
->addHiddenInput('parent', $new_paste->getParentPHID())
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel('Title')
|
||||
->setValue($new_paste->getTitle())
|
||||
->setName('title'))
|
||||
->appendChild($language_select)
|
||||
->appendChild(
|
||||
id(new AphrontFormTextAreaControl())
|
||||
->setLabel('Text')
|
||||
->setError($this->getErrorText())
|
||||
->setValue($this->getPasteText())
|
||||
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
|
||||
->setName('text'))
|
||||
|
||||
/* TODO: Doesn't have any useful options yet.
|
||||
->appendChild(
|
||||
id(new AphrontFormPolicyControl())
|
||||
->setLabel('Visible To')
|
||||
->setUser($user)
|
||||
->setValue(
|
||||
$new_paste->getPolicy(PhabricatorPolicyCapability::CAN_VIEW))
|
||||
->setName('policy'))
|
||||
*/
|
||||
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->addCancelButton('/paste/')
|
||||
->setValue('Create Paste'));
|
||||
|
||||
$create_panel = new AphrontPanelView();
|
||||
$create_panel->setWidth(AphrontPanelView::WIDTH_FULL);
|
||||
$create_panel->setHeader('Create a Paste');
|
||||
$create_panel->appendChild($form);
|
||||
|
||||
return $create_panel;
|
||||
}
|
||||
|
||||
private function renderPasteList(array $pastes, $header, $pager) {
|
||||
assert_instances_of($pastes, 'PhabricatorPaste');
|
||||
|
||||
$phids = mpull($pastes, 'getAuthorPHID');
|
||||
$handles = array();
|
||||
|
@ -318,8 +321,7 @@ final class PhabricatorPasteListController extends PhabricatorPasteController {
|
|||
if ($phids) {
|
||||
$files = id(new PhabricatorFile())->loadAllWhere(
|
||||
'phid in (%Ls)',
|
||||
$phids
|
||||
);
|
||||
$phids);
|
||||
if ($files) {
|
||||
$file_uris = mpull($files, 'getBestURI', 'getPHID');
|
||||
}
|
||||
|
@ -363,59 +365,7 @@ final class PhabricatorPasteListController extends PhabricatorPasteController {
|
|||
);
|
||||
}
|
||||
|
||||
return array($paste_list_rows, $pager);
|
||||
}
|
||||
|
||||
private function renderCreatePaste() {
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
$new_paste = $this->getPaste();
|
||||
|
||||
$form = new AphrontFormView();
|
||||
|
||||
$available_languages = PhabricatorEnv::getEnvConfig(
|
||||
'pygments.dropdown-choices');
|
||||
asort($available_languages);
|
||||
$language_select = id(new AphrontFormSelectControl())
|
||||
->setLabel('Language')
|
||||
->setName('language')
|
||||
->setValue($new_paste->getLanguage())
|
||||
->setOptions($available_languages);
|
||||
|
||||
$form
|
||||
->setUser($user)
|
||||
->setAction($request->getRequestURI()->getPath())
|
||||
->addHiddenInput('parent', $new_paste->getParentPHID())
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel('Title')
|
||||
->setValue($new_paste->getTitle())
|
||||
->setName('title'))
|
||||
->appendChild($language_select)
|
||||
->appendChild(
|
||||
id(new AphrontFormTextAreaControl())
|
||||
->setLabel('Text')
|
||||
->setError($this->getErrorText())
|
||||
->setValue($this->getPasteText())
|
||||
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
|
||||
->setName('text'))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->addCancelButton('/paste/')
|
||||
->setValue('Create Paste'));
|
||||
|
||||
$create_panel = new AphrontPanelView();
|
||||
$create_panel->setWidth(AphrontPanelView::WIDTH_FULL);
|
||||
$create_panel->setHeader('Create a Paste');
|
||||
$create_panel->appendChild($form);
|
||||
|
||||
return $create_panel;
|
||||
}
|
||||
|
||||
private function renderPasteList($paste_list_rows,
|
||||
$header,
|
||||
$pager = null) {
|
||||
$table = new AphrontTableView($paste_list_rows);
|
||||
$table->setHeaders(
|
||||
array(
|
||||
|
|
|
@ -9,10 +9,11 @@
|
|||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||
phutil_require_module('phabricator', 'applications/files/storage/file');
|
||||
phutil_require_module('phabricator', 'applications/paste/controller/base');
|
||||
phutil_require_module('phabricator', 'applications/paste/query/paste');
|
||||
phutil_require_module('phabricator', 'applications/paste/storage/paste');
|
||||
phutil_require_module('phabricator', 'applications/phid/handle/data');
|
||||
phutil_require_module('phabricator', 'infrastructure/env');
|
||||
phutil_require_module('phabricator', 'view/control/pager');
|
||||
phutil_require_module('phabricator', 'view/control/idpager');
|
||||
phutil_require_module('phabricator', 'view/control/table');
|
||||
phutil_require_module('phabricator', 'view/form/base');
|
||||
phutil_require_module('phabricator', 'view/form/control/select');
|
||||
|
|
|
@ -29,7 +29,11 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController {
|
|||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
$paste = id(new PhabricatorPaste())->load($this->id);
|
||||
$paste = id(new PhabricatorPasteQuery())
|
||||
->setViewer($user)
|
||||
->withPasteIDs(array($this->id))
|
||||
->executeOne();
|
||||
|
||||
if (!$paste) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ phutil_require_module('phabricator', 'aphront/response/404');
|
|||
phutil_require_module('phabricator', 'applications/files/storage/file');
|
||||
phutil_require_module('phabricator', 'applications/markup/syntax');
|
||||
phutil_require_module('phabricator', 'applications/paste/controller/base');
|
||||
phutil_require_module('phabricator', 'applications/paste/query/paste');
|
||||
phutil_require_module('phabricator', 'applications/paste/storage/paste');
|
||||
phutil_require_module('phabricator', 'infrastructure/celerity/api');
|
||||
phutil_require_module('phabricator', 'infrastructure/javelin/api');
|
||||
|
|
73
src/applications/paste/query/paste/PhabricatorPasteQuery.php
Normal file
73
src/applications/paste/query/paste/PhabricatorPasteQuery.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorPasteQuery extends PhabricatorIDPagedPolicyQuery {
|
||||
|
||||
private $pasteIDs;
|
||||
private $authorPHIDs;
|
||||
|
||||
public function withPasteIDs(array $ids) {
|
||||
$this->pasteIDs = $ids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withAuthorPHIDs(array $phids) {
|
||||
$this->authorPHIDs = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function loadPage() {
|
||||
$table = new PhabricatorPaste();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
|
||||
$data = queryfx_all(
|
||||
$conn_r,
|
||||
'SELECT paste.* FROM %T paste %Q %Q %Q',
|
||||
$table->getTableName(),
|
||||
$this->buildWhereClause($conn_r),
|
||||
$this->buildOrderClause($conn_r),
|
||||
$this->buildLimitClause($conn_r));
|
||||
|
||||
$results = $table->loadAllFromArray($data);
|
||||
|
||||
return $this->processResults($results);
|
||||
}
|
||||
|
||||
protected function buildWhereClause($conn_r) {
|
||||
$where = array();
|
||||
|
||||
$where[] = $this->buildPagingClause($conn_r);
|
||||
|
||||
if ($this->pasteIDs) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'id IN (%Ls)',
|
||||
$this->pasteIDs);
|
||||
}
|
||||
|
||||
if ($this->authorPHIDs) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'authorPHID IN (%Ls)',
|
||||
$this->authorPHIDs);
|
||||
}
|
||||
|
||||
return $this->formatWhereClause($where);
|
||||
}
|
||||
|
||||
}
|
15
src/applications/paste/query/paste/__init__.php
Normal file
15
src/applications/paste/query/paste/__init__.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/paste/storage/paste');
|
||||
phutil_require_module('phabricator', 'infrastructure/query/policy/idpaged');
|
||||
phutil_require_module('phabricator', 'storage/qsprintf');
|
||||
phutil_require_module('phabricator', 'storage/queryfx');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorPasteQuery.php');
|
|
@ -16,7 +16,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorPaste extends PhabricatorPasteDAO {
|
||||
final class PhabricatorPaste extends PhabricatorPasteDAO
|
||||
implements PhabricatorPolicyInterface {
|
||||
|
||||
protected $phid;
|
||||
protected $title;
|
||||
|
@ -36,4 +37,18 @@ final class PhabricatorPaste extends PhabricatorPasteDAO {
|
|||
PhabricatorPHIDConstants::PHID_TYPE_PSTE);
|
||||
}
|
||||
|
||||
public function getCapabilities() {
|
||||
return array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
);
|
||||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
return PhabricatorPolicies::POLICY_USER;
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
|
||||
return ($user->getPHID() == $this->getAuthorPHID());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
phutil_require_module('phabricator', 'applications/paste/storage/base');
|
||||
phutil_require_module('phabricator', 'applications/phid/constants');
|
||||
phutil_require_module('phabricator', 'applications/phid/storage/phid');
|
||||
phutil_require_module('phabricator', 'applications/policy/constants/capability');
|
||||
phutil_require_module('phabricator', 'applications/policy/constants/policy');
|
||||
phutil_require_module('phabricator', 'applications/policy/interface/policy');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorPaste.php');
|
||||
|
|
138
src/applications/policy/__tests__/PhabricatorPolicyTestCase.php
Normal file
138
src/applications/policy/__tests__/PhabricatorPolicyTestCase.php
Normal file
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorPolicyTestCase extends PhabricatorTestCase {
|
||||
|
||||
/**
|
||||
* Verify that any user can view an object with POLICY_PUBLIC.
|
||||
*/
|
||||
public function testPublicPolicy() {
|
||||
$viewer = new PhabricatorUser();
|
||||
|
||||
$object = new PhabricatorPolicyTestObject();
|
||||
$object->setCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
));
|
||||
$object->setPolicies(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW =>
|
||||
PhabricatorPolicies::POLICY_PUBLIC,
|
||||
));
|
||||
|
||||
$query = new PhabricatorPolicyTestQuery();
|
||||
$query->setResults(array($object));
|
||||
$query->setViewer($viewer);
|
||||
$result = $query->executeOne();
|
||||
|
||||
$this->assertEqual($object, $result, 'Policy: Public');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verify that any logged-in user can view an object with POLICY_USER, but
|
||||
* logged-out users can not.
|
||||
*/
|
||||
public function testUsersPolicy() {
|
||||
$viewer = new PhabricatorUser();
|
||||
|
||||
$object = new PhabricatorPolicyTestObject();
|
||||
$object->setCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
));
|
||||
$object->setPolicies(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW =>
|
||||
PhabricatorPolicies::POLICY_USER,
|
||||
));
|
||||
|
||||
$query = new PhabricatorPolicyTestQuery();
|
||||
$query->setResults(array($object));
|
||||
$query->setViewer($viewer);
|
||||
|
||||
$caught = null;
|
||||
try {
|
||||
$query->executeOne();
|
||||
} catch (PhabricatorPolicyException $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
|
||||
$this->assertEqual(
|
||||
true,
|
||||
($caught instanceof PhabricatorPolicyException),
|
||||
'Policy: Users rejects logged out users.');
|
||||
|
||||
$viewer->setPHID(1);
|
||||
$result = $query->executeOne();
|
||||
$this->assertEqual(
|
||||
$object,
|
||||
$result,
|
||||
'Policy: Users');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verify that no one can view an object with POLICY_NOONE.
|
||||
*/
|
||||
public function testNoOnePolicy() {
|
||||
$viewer = new PhabricatorUser();
|
||||
|
||||
$object = new PhabricatorPolicyTestObject();
|
||||
$object->setCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
));
|
||||
$object->setPolicies(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW =>
|
||||
PhabricatorPolicies::POLICY_NOONE,
|
||||
));
|
||||
|
||||
$query = new PhabricatorPolicyTestQuery();
|
||||
$query->setResults(array($object));
|
||||
$query->setViewer($viewer);
|
||||
|
||||
$caught = null;
|
||||
try {
|
||||
$query->executeOne();
|
||||
} catch (PhabricatorPolicyException $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
|
||||
$this->assertEqual(
|
||||
true,
|
||||
($caught instanceof PhabricatorPolicyException),
|
||||
'Policy: No One rejects logged out users.');
|
||||
|
||||
$viewer->setPHID(1);
|
||||
|
||||
$caught = null;
|
||||
try {
|
||||
$query->executeOne();
|
||||
} catch (PhabricatorPolicyException $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
|
||||
$this->assertEqual(
|
||||
true,
|
||||
($caught instanceof PhabricatorPolicyException),
|
||||
'Policy: No One rejects logged-in users.');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Configurable test object for implementing Policy unit tests.
|
||||
*/
|
||||
final class PhabricatorPolicyTestObject
|
||||
implements PhabricatorPolicyInterface {
|
||||
|
||||
private $capabilities = array();
|
||||
private $policies = array();
|
||||
private $automaticCapabilities = array();
|
||||
|
||||
public function getCapabilities() {
|
||||
return $this->capabilities;
|
||||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
return idx($this->policies, $capability);
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
$auto = idx($this->automaticCapabilities, $capability, array());
|
||||
return idx($auto, $viewer->getPHID());
|
||||
}
|
||||
|
||||
public function setCapabilities(array $capabilities) {
|
||||
$this->capabilities = $capabilities;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPolicies(array $policy_map) {
|
||||
$this->policies = $policy_map;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setAutomaticCapabilities(array $auto_map) {
|
||||
$this->automaticCapabilities = $auto_map;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Configurable test query for implementing Policy unit tests.
|
||||
*/
|
||||
final class PhabricatorPolicyTestQuery
|
||||
extends PhabricatorPolicyQuery {
|
||||
|
||||
private $results;
|
||||
|
||||
public function setResults(array $results) {
|
||||
$this->results = $results;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function loadPage() {
|
||||
return $this->results;
|
||||
}
|
||||
|
||||
public function nextPage(array $page) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
21
src/applications/policy/__tests__/__init__.php
Normal file
21
src/applications/policy/__tests__/__init__.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/people/storage/user');
|
||||
phutil_require_module('phabricator', 'applications/policy/constants/capability');
|
||||
phutil_require_module('phabricator', 'applications/policy/constants/policy');
|
||||
phutil_require_module('phabricator', 'applications/policy/interface/policy');
|
||||
phutil_require_module('phabricator', 'infrastructure/query/policy/base');
|
||||
phutil_require_module('phabricator', 'infrastructure/testing/testcase');
|
||||
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorPolicyTestCase.php');
|
||||
phutil_require_source('PhabricatorPolicyTestObject.php');
|
||||
phutil_require_source('PhabricatorPolicyTestQuery.php');
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
abstract class PhabricatorPolicyConstants {
|
||||
|
||||
}
|
10
src/applications/policy/constants/base/__init__.php
Normal file
10
src/applications/policy/constants/base/__init__.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorPolicyConstants.php');
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorPolicyCapability extends PhabricatorPolicyConstants {
|
||||
|
||||
const CAN_VIEW = 'view';
|
||||
|
||||
}
|
12
src/applications/policy/constants/capability/__init__.php
Normal file
12
src/applications/policy/constants/capability/__init__.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/policy/constants/base');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorPolicyCapability.php');
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorPolicies extends PhabricatorPolicyConstants {
|
||||
|
||||
const POLICY_PUBLIC = 'public';
|
||||
const POLICY_USER = 'users';
|
||||
const POLICY_NOONE = 'no-one';
|
||||
|
||||
}
|
12
src/applications/policy/constants/policy/__init__.php
Normal file
12
src/applications/policy/constants/policy/__init__.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/policy/constants/base');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorPolicies.php');
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorPolicyException extends Exception {
|
||||
|
||||
}
|
10
src/applications/policy/exception/base/__init__.php
Normal file
10
src/applications/policy/exception/base/__init__.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorPolicyException.php');
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorPolicyFilter {
|
||||
|
||||
private $viewer;
|
||||
private $objects;
|
||||
private $capability;
|
||||
private $raisePolicyExceptions;
|
||||
|
||||
public function setViewer(PhabricatorUser $user) {
|
||||
$this->viewer = $user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCapability($capability) {
|
||||
$this->capability = $capability;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function raisePolicyExceptions($raise) {
|
||||
$this->raisePolicyExceptions = $raise;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function apply(array $objects) {
|
||||
assert_instances_of($objects, 'PhabricatorPolicyInterface');
|
||||
|
||||
$viewer = $this->viewer;
|
||||
$capability = $this->capability;
|
||||
|
||||
if (!$viewer || !$capability) {
|
||||
throw new Exception(
|
||||
'Call setViewer() and setCapability() before apply()!');
|
||||
}
|
||||
|
||||
$filtered = array();
|
||||
|
||||
foreach ($objects as $key => $object) {
|
||||
$object_capabilities = $object->getCapabilities();
|
||||
|
||||
if (!in_array($capability, $object_capabilities)) {
|
||||
throw new Exception(
|
||||
"Testing for capability '{$capability}' on an object which does not ".
|
||||
"have that capability!");
|
||||
}
|
||||
|
||||
if ($object->hasAutomaticCapability($capability, $this->viewer)) {
|
||||
$filtered[$key] = $object;
|
||||
continue;
|
||||
}
|
||||
|
||||
$policy = $object->getPolicy($capability);
|
||||
|
||||
switch ($policy) {
|
||||
case PhabricatorPolicies::POLICY_PUBLIC:
|
||||
$filtered[$key] = $object;
|
||||
break;
|
||||
case PhabricatorPolicies::POLICY_USER:
|
||||
if ($viewer->getPHID()) {
|
||||
$filtered[$key] = $object;
|
||||
} else {
|
||||
$this->rejectObject($object, $policy);
|
||||
}
|
||||
break;
|
||||
case PhabricatorPolicies::POLICY_NOONE:
|
||||
$this->rejectObject($object, $policy);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Object has unknown policy '{$policy}'!");
|
||||
}
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
private function rejectObject($object, $policy) {
|
||||
if (!$this->raisePolicyExceptions) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message = "You do not have permission to view this object.";
|
||||
|
||||
switch ($policy) {
|
||||
case PhabricatorPolicies::POLICY_PUBLIC:
|
||||
$who = "This is curious, since anyone can view the object.";
|
||||
break;
|
||||
case PhabricatorPolicies::POLICY_USER:
|
||||
$who = "To view this object, you must be logged in.";
|
||||
break;
|
||||
case PhabricatorPolicies::POLICY_NOONE:
|
||||
$who = "No one can view this object.";
|
||||
break;
|
||||
default:
|
||||
$who = "It is unclear who can view this object.";
|
||||
break;
|
||||
}
|
||||
|
||||
throw new PhabricatorPolicyException("{$message} {$who}");
|
||||
}
|
||||
}
|
15
src/applications/policy/filter/policy/__init__.php
Normal file
15
src/applications/policy/filter/policy/__init__.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/policy/constants/policy');
|
||||
phutil_require_module('phabricator', 'applications/policy/exception/base');
|
||||
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorPolicyFilter.php');
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
interface PhabricatorPolicyInterface {
|
||||
|
||||
public function getCapabilities();
|
||||
public function getPolicy($capability);
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer);
|
||||
|
||||
}
|
10
src/applications/policy/interface/policy/__init__.php
Normal file
10
src/applications/policy/interface/policy/__init__.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorPolicyInterface.php');
|
|
@ -21,9 +21,12 @@ abstract class PhabricatorQuery {
|
|||
abstract public function execute();
|
||||
|
||||
final protected function formatWhereClause(array $parts) {
|
||||
$parts = array_filter($parts);
|
||||
|
||||
if (!$parts) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return 'WHERE ('.implode(') AND (', $parts).')';
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
abstract class PhabricatorPolicyQuery extends PhabricatorQuery {
|
||||
|
||||
private $limit;
|
||||
private $viewer;
|
||||
private $raisePolicyExceptions;
|
||||
|
||||
final public function setViewer($viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getViewer() {
|
||||
return $this->viewer;
|
||||
}
|
||||
|
||||
final public function setLimit($limit) {
|
||||
$this->limit = $limit;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getLimit() {
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
final public function executeOne() {
|
||||
|
||||
$this->raisePolicyExceptions = true;
|
||||
try {
|
||||
$results = $this->execute();
|
||||
} catch (Exception $ex) {
|
||||
$this->raisePolicyExceptions = false;
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
if (count($results) > 1) {
|
||||
throw new Exception("Expected a single result!");
|
||||
}
|
||||
return head($results);
|
||||
}
|
||||
|
||||
final public function execute() {
|
||||
if (!$this->viewer) {
|
||||
throw new Exception("Call setViewer() before execute()!");
|
||||
}
|
||||
|
||||
$results = array();
|
||||
|
||||
$filter = new PhabricatorPolicyFilter();
|
||||
$filter->setViewer($this->viewer);
|
||||
$filter->setCapability(PhabricatorPolicyCapability::CAN_VIEW);
|
||||
|
||||
$filter->raisePolicyExceptions($this->raisePolicyExceptions);
|
||||
|
||||
do {
|
||||
$page = $this->loadPage();
|
||||
|
||||
$visible = $filter->apply($page);
|
||||
foreach ($visible as $key => $result) {
|
||||
$results[$key] = $result;
|
||||
if ($this->getLimit() && count($results) >= $this->getLimit()) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->getLimit() || (count($page) < $this->getLimit())) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->nextPage($page);
|
||||
} while (true);
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
abstract protected function loadPage();
|
||||
abstract protected function nextPage(array $page);
|
||||
|
||||
}
|
16
src/infrastructure/query/policy/base/__init__.php
Normal file
16
src/infrastructure/query/policy/base/__init__.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/policy/constants/capability');
|
||||
phutil_require_module('phabricator', 'applications/policy/filter/policy');
|
||||
phutil_require_module('phabricator', 'infrastructure/query/base');
|
||||
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorPolicyQuery.php');
|
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A query class which uses ID-based paging. This paging is much more performant
|
||||
* than offset-based paging in the presence of policy filtering.
|
||||
*/
|
||||
abstract class PhabricatorIDPagedPolicyQuery extends PhabricatorPolicyQuery {
|
||||
|
||||
private $afterID;
|
||||
private $beforeID;
|
||||
|
||||
protected function getPagingColumn() {
|
||||
return 'id';
|
||||
}
|
||||
|
||||
protected function getPagingValue($result) {
|
||||
return $result->getID();
|
||||
}
|
||||
|
||||
protected function nextPage(array $page) {
|
||||
if ($this->beforeID) {
|
||||
$this->beforeID = $this->getPagingValue(head($page));
|
||||
} else {
|
||||
$this->afterID = $this->getPagingValue(last($page));
|
||||
}
|
||||
}
|
||||
|
||||
final public function setAfterID($object_id) {
|
||||
$this->afterID = $object_id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function setBeforeID($object_id) {
|
||||
$this->beforeID = $object_id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final protected function buildLimitClause(AphrontDatabaseConnection $conn_r) {
|
||||
if ($this->getLimit()) {
|
||||
return qsprintf($conn_r, 'LIMIT %d', $this->getLimit());
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
final protected function buildPagingClause(
|
||||
AphrontDatabaseConnection $conn_r) {
|
||||
|
||||
if ($this->beforeID) {
|
||||
return qsprintf(
|
||||
$conn_r,
|
||||
'%C > %s',
|
||||
$this->getPagingColumn(),
|
||||
$this->beforeID);
|
||||
} else if ($this->afterID) {
|
||||
return qsprintf(
|
||||
$conn_r,
|
||||
'%C < %s',
|
||||
$this->getPagingColumn(),
|
||||
$this->afterID);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
final protected function buildOrderClause(AphrontDatabaseConnection $conn_r) {
|
||||
if ($this->beforeID) {
|
||||
return qsprintf(
|
||||
$conn_r,
|
||||
'ORDER BY %C ASC',
|
||||
$this->getPagingColumn());
|
||||
} else {
|
||||
return qsprintf(
|
||||
$conn_r,
|
||||
'ORDER BY %C DESC',
|
||||
$this->getPagingColumn());
|
||||
}
|
||||
}
|
||||
|
||||
final protected function processResults(array $results) {
|
||||
if ($this->beforeID) {
|
||||
$results = array_reverse($results, $preserve_keys = true);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
|
||||
final public function executeWithPager(AphrontIDPagerView $pager) {
|
||||
$this->setLimit($pager->getPageSize() + 1);
|
||||
|
||||
if ($pager->getAfterID()) {
|
||||
$this->setAfterID($pager->getAfterID());
|
||||
} else if ($pager->getBeforeID()) {
|
||||
$this->setBeforeID($pager->getBeforeID());
|
||||
}
|
||||
|
||||
$results = $this->execute();
|
||||
|
||||
$sliced_results = $pager->sliceResults($results);
|
||||
|
||||
if ($this->beforeID || (count($results) > $pager->getPageSize())) {
|
||||
$pager->setNextPageID($this->getPagingValue(last($sliced_results)));
|
||||
}
|
||||
|
||||
if ($this->afterID ||
|
||||
($this->beforeID && (count($results) > $pager->getPageSize()))) {
|
||||
$pager->setPrevPageID($this->getPagingValue(head($sliced_results)));
|
||||
}
|
||||
|
||||
return $sliced_results;
|
||||
}
|
||||
|
||||
}
|
15
src/infrastructure/query/policy/idpaged/__init__.php
Normal file
15
src/infrastructure/query/policy/idpaged/__init__.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'infrastructure/query/policy/base');
|
||||
phutil_require_module('phabricator', 'storage/qsprintf');
|
||||
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorIDPagedPolicyQuery.php');
|
142
src/view/control/idpager/AphrontIDPagerView.php
Normal file
142
src/view/control/idpager/AphrontIDPagerView.php
Normal file
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class AphrontIDPagerView extends AphrontView {
|
||||
|
||||
private $afterID;
|
||||
private $beforeID;
|
||||
|
||||
private $pageSize = 100;
|
||||
|
||||
private $nextPageID;
|
||||
private $prevPageID;
|
||||
|
||||
private $uri;
|
||||
|
||||
final public function setPageSize($page_size) {
|
||||
$this->pageSize = max(1, $page_size);
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getPageSize() {
|
||||
return $this->pageSize;
|
||||
}
|
||||
|
||||
final public function setURI(PhutilURI $uri) {
|
||||
$this->uri = $uri;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function readFromRequest(AphrontRequest $request) {
|
||||
$this->uri = $request->getRequestURI();
|
||||
$this->afterID = $request->getStr('after');
|
||||
$this->beforeID = $request->getStr('before');
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function setAfterID($after_id) {
|
||||
$this->afterID = $after_id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getAfterID() {
|
||||
return $this->afterID;
|
||||
}
|
||||
|
||||
final public function setBeforeID($before_id) {
|
||||
$this->beforeID = $before_id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getBeforeID() {
|
||||
return $this->beforeID;
|
||||
}
|
||||
|
||||
final public function setNextPageID($next_page_id) {
|
||||
$this->nextPageID = $next_page_id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getNextPageID() {
|
||||
return $this->nextPageID;
|
||||
}
|
||||
|
||||
final public function setPrevPageID($prev_page_id) {
|
||||
$this->prevPageID = $prev_page_id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getPrevPageID() {
|
||||
return $this->prevPageID;
|
||||
}
|
||||
|
||||
final public function sliceResults(array $results) {
|
||||
if (count($results) > $this->getPageSize()) {
|
||||
$results = array_slice($results, 0, $this->getPageSize(), true);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
if (!$this->uri) {
|
||||
throw new Exception(
|
||||
"You must call setURI() before you can call render().");
|
||||
}
|
||||
|
||||
$links = array();
|
||||
|
||||
if ($this->beforeID || $this->afterID) {
|
||||
$links[] = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $this->uri
|
||||
->alter('before', null)
|
||||
->alter('after', null),
|
||||
),
|
||||
"\xC2\xAB First");
|
||||
}
|
||||
|
||||
if ($this->prevPageID) {
|
||||
$links[] = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $this->uri
|
||||
->alter('after', null)
|
||||
->alter('before', $this->prevPageID),
|
||||
),
|
||||
"\xE2\x80\xB9 Prev");
|
||||
}
|
||||
|
||||
if ($this->nextPageID) {
|
||||
$links[] = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $this->uri
|
||||
->alter('after', $this->nextPageID)
|
||||
->alter('before', null),
|
||||
),
|
||||
"Next \xE2\x80\xBA");
|
||||
}
|
||||
|
||||
return
|
||||
'<div class="aphront-pager-view">'.
|
||||
implode('', $links).
|
||||
'</div>';
|
||||
}
|
||||
|
||||
}
|
14
src/view/control/idpager/__init__.php
Normal file
14
src/view/control/idpager/__init__.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'view/base');
|
||||
|
||||
phutil_require_module('phutil', 'markup');
|
||||
|
||||
|
||||
phutil_require_source('AphrontIDPagerView.php');
|
52
src/view/form/control/policy/AphrontFormPolicyControl.php
Normal file
52
src/view/form/control/policy/AphrontFormPolicyControl.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class AphrontFormPolicyControl extends AphrontFormControl {
|
||||
|
||||
public function setUser(PhabricatorUser $user) {
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUser() {
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
protected function getCustomControlClass() {
|
||||
return 'aphront-form-control-policy';
|
||||
}
|
||||
|
||||
private function getOptions() {
|
||||
return array(
|
||||
PhabricatorPolicies::POLICY_USER => 'All Users',
|
||||
);
|
||||
}
|
||||
|
||||
protected function renderInput() {
|
||||
return AphrontFormSelectControl::renderSelectTag(
|
||||
$this->getValue(),
|
||||
$this->getOptions(),
|
||||
array(
|
||||
'name' => $this->getName(),
|
||||
'disabled' => $this->getDisabled() ? 'disabled' : null,
|
||||
'id' => $this->getID(),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
14
src/view/form/control/policy/__init__.php
Normal file
14
src/view/form/control/policy/__init__.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/policy/constants/policy');
|
||||
phutil_require_module('phabricator', 'view/form/control/base');
|
||||
phutil_require_module('phabricator', 'view/form/control/select');
|
||||
|
||||
|
||||
phutil_require_source('AphrontFormPolicyControl.php');
|
|
@ -89,3 +89,7 @@
|
|||
padding-bottom: .5em;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.aphront-access-dialog {
|
||||
width: 50%;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue