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:
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user