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
2012-04-14 19:13:29 +02:00
|
|
|
<?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.
|
|
|
|
*/
|
|
|
|
|
2012-08-08 21:15:58 +02:00
|
|
|
/**
|
|
|
|
* A @{class:PhabricatorQuery} which filters results according to visibility
|
|
|
|
* policies for the querying user. Broadly, this class allows you to implement
|
|
|
|
* a query that returns only objects the user is allowed to see.
|
|
|
|
*
|
|
|
|
* $results = id(new ExampleQuery())
|
|
|
|
* ->setViewer($user)
|
|
|
|
* ->withConstraint($example)
|
|
|
|
* ->execute();
|
|
|
|
*
|
|
|
|
* Normally, you should extend @{class:PhabricatorCursorPagedPolicyQuery}, not
|
|
|
|
* this class. @{class:PhabricatorCursorPagedPolicyQuery} provides a more
|
|
|
|
* practical interface for building usable queries against most object types.
|
|
|
|
*
|
|
|
|
* NOTE: Although this class extends @{class:PhabricatorOffsetPagedQuery},
|
|
|
|
* offset paging with policy filtering is not efficient. All results must be
|
|
|
|
* loaded into the application and filtered here: skipping `N` rows via offset
|
|
|
|
* is an `O(N)` operation with a large constant. Prefer cursor-based paging
|
|
|
|
* with @{class:PhabricatorCursorPagedPolicyQuery}, which can filter far more
|
|
|
|
* efficiently in MySQL.
|
|
|
|
*
|
|
|
|
* @task config Query Configuration
|
|
|
|
* @task exec Executing Queries
|
|
|
|
* @task policyimpl Policy Query Implementation
|
|
|
|
*/
|
|
|
|
abstract class PhabricatorPolicyQuery extends PhabricatorOffsetPagedQuery {
|
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
2012-04-14 19:13:29 +02:00
|
|
|
|
|
|
|
private $viewer;
|
|
|
|
private $raisePolicyExceptions;
|
2012-08-08 21:15:58 +02:00
|
|
|
private $rawResultLimit;
|
|
|
|
|
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
2012-04-14 19:13:29 +02:00
|
|
|
|
2012-08-08 21:15:58 +02:00
|
|
|
/* -( Query Configuration )------------------------------------------------ */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the viewer who is executing the query. Results will be filtered
|
|
|
|
* according to the viewer's capabilities. You must set a viewer to execute
|
|
|
|
* a policy query.
|
|
|
|
*
|
|
|
|
* @param PhabricatorUser The viewing user.
|
|
|
|
* @return this
|
|
|
|
* @task config
|
|
|
|
*/
|
|
|
|
final public function setViewer(PhabricatorUser $viewer) {
|
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
2012-04-14 19:13:29 +02:00
|
|
|
$this->viewer = $viewer;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-08-08 21:15:58 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the query's viewer.
|
|
|
|
*
|
|
|
|
* @return PhabricatorUser The viewing user.
|
|
|
|
* @task config
|
|
|
|
*/
|
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
2012-04-14 19:13:29 +02:00
|
|
|
final public function getViewer() {
|
|
|
|
return $this->viewer;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-08-08 21:15:58 +02:00
|
|
|
/* -( Query Execution )---------------------------------------------------- */
|
|
|
|
|
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
2012-04-14 19:13:29 +02:00
|
|
|
|
2012-08-08 21:15:58 +02:00
|
|
|
/**
|
|
|
|
* Execute the query, expecting a single result. This method simplifies
|
|
|
|
* loading objects for detail pages or edit views.
|
|
|
|
*
|
|
|
|
* // Load one result by ID.
|
|
|
|
* $obj = id(new ExampleQuery())
|
|
|
|
* ->setViewer($user)
|
|
|
|
* ->withIDs(array($id))
|
|
|
|
* ->executeOne();
|
|
|
|
* if (!$obj) {
|
|
|
|
* return new Aphront404Response();
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* If zero results match the query, this method returns `null`.
|
|
|
|
*
|
|
|
|
* If one result matches the query, this method returns that result.
|
|
|
|
*
|
|
|
|
* If two or more results match the query, this method throws an exception.
|
|
|
|
* You should use this method only when the query constraints guarantee at
|
|
|
|
* most one match (e.g., selecting a specific ID or PHID).
|
|
|
|
*
|
|
|
|
* If one result matches the query but it is caught by the policy filter (for
|
|
|
|
* example, the user is trying to view or edit an object which exists but
|
|
|
|
* which they do not have permission to see) a policy exception is thrown.
|
|
|
|
*
|
|
|
|
* @return mixed Single result, or null.
|
|
|
|
* @task exec
|
|
|
|
*/
|
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
2012-04-14 19:13:29 +02:00
|
|
|
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!");
|
|
|
|
}
|
2012-08-08 21:15:58 +02:00
|
|
|
|
|
|
|
if (!$results) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
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
2012-04-14 19:13:29 +02:00
|
|
|
return head($results);
|
|
|
|
}
|
|
|
|
|
2012-08-08 21:15:58 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Execute the query, loading all visible results.
|
|
|
|
*
|
|
|
|
* @return list<PhabricatorPolicyInterface> Result objects.
|
|
|
|
* @task exec
|
|
|
|
*/
|
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
2012-04-14 19:13:29 +02:00
|
|
|
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);
|
|
|
|
|
2012-08-08 21:15:58 +02:00
|
|
|
$offset = (int)$this->getOffset();
|
|
|
|
$limit = (int)$this->getLimit();
|
|
|
|
$count = 0;
|
|
|
|
|
|
|
|
$need = null;
|
|
|
|
if ($offset) {
|
|
|
|
$need = $offset + $limit;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->willExecute();
|
|
|
|
|
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
2012-04-14 19:13:29 +02:00
|
|
|
do {
|
2012-08-08 21:15:58 +02:00
|
|
|
|
|
|
|
// Figure out how many results to load. "0" means "all results".
|
|
|
|
$load = 0;
|
|
|
|
if ($need && ($count < $offset)) {
|
|
|
|
// This cap is just an arbitrary limit to keep memory usage from going
|
|
|
|
// crazy for large offsets; we can't execute them efficiently, but
|
|
|
|
// it should be possible to execute them without crashing.
|
|
|
|
$load = min($need, 1024);
|
|
|
|
} else if ($limit) {
|
|
|
|
// Otherwise, just load the number of rows we're after. Note that it
|
|
|
|
// might be more efficient to load more rows than this (if we expect
|
|
|
|
// about 5% of objects to be filtered, loading 105% of the limit might
|
|
|
|
// be better) or fewer rows than this (if we already have 95 rows and
|
|
|
|
// only need 100, loading only 5 rows might be better), but we currently
|
|
|
|
// just use the simplest heuristic since we don't have enough data
|
|
|
|
// about policy queries in the real world to tweak it.
|
|
|
|
$load = $limit;
|
|
|
|
}
|
|
|
|
$this->rawResultLimit = $load;
|
|
|
|
|
|
|
|
|
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
2012-04-14 19:13:29 +02:00
|
|
|
$page = $this->loadPage();
|
|
|
|
|
|
|
|
$visible = $filter->apply($page);
|
|
|
|
foreach ($visible as $key => $result) {
|
2012-08-08 21:15:58 +02:00
|
|
|
++$count;
|
|
|
|
|
|
|
|
// If we have an offset, we just ignore that many results and start
|
|
|
|
// storing them only once we've hit the offset. This reduces memory
|
|
|
|
// requirements for large offsets, compared to storing them all and
|
|
|
|
// slicing them away later.
|
|
|
|
if ($count > $offset) {
|
|
|
|
$results[$key] = $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($need && ($count >= $need)) {
|
|
|
|
// If we have all the rows we need, break out of the paging query.
|
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
2012-04-14 19:13:29 +02:00
|
|
|
break 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-08 21:15:58 +02:00
|
|
|
if (!$load) {
|
|
|
|
// If we don't have a load count, we loaded all the results. We do
|
|
|
|
// not need to load another page.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count($page) < $load) {
|
|
|
|
// If we have a load count but the unfiltered results contained fewer
|
|
|
|
// objects, we know this was the last page of objects; we do not need
|
|
|
|
// to load another page because we can deduce it would be empty.
|
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
2012-04-14 19:13:29 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->nextPage($page);
|
|
|
|
} while (true);
|
|
|
|
|
|
|
|
return $results;
|
|
|
|
}
|
|
|
|
|
2012-08-08 21:15:58 +02:00
|
|
|
|
|
|
|
/* -( Policy Query Implementation )---------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the number of results @{method:loadPage} should load. If the value is
|
|
|
|
* 0, @{method:loadPage} should load all available results.
|
|
|
|
*
|
|
|
|
* @return int The number of results to load, or 0 for all results.
|
|
|
|
* @task policyimpl
|
|
|
|
*/
|
|
|
|
final protected function getRawResultLimit() {
|
|
|
|
return $this->rawResultLimit;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hook invoked before query execution. Generally, implementations should
|
|
|
|
* reset any internal cursors.
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
* @task policyimpl
|
|
|
|
*/
|
|
|
|
protected function willExecute() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load a raw page of results. Generally, implementations should load objects
|
|
|
|
* from the database. They should attempt to return the number of results
|
|
|
|
* hinted by @{method:getRawResultLimit}.
|
|
|
|
*
|
|
|
|
* @return list<PhabricatorPolicyInterface> List of filterable policy objects.
|
|
|
|
* @task policyimpl
|
|
|
|
*/
|
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
2012-04-14 19:13:29 +02:00
|
|
|
abstract protected function loadPage();
|
2012-08-08 21:15:58 +02:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update internal state so that the next call to @{method:loadPage} will
|
|
|
|
* return new results. Generally, you should adjust a cursor position based
|
|
|
|
* on the provided result page.
|
|
|
|
*
|
|
|
|
* @param list<PhabricatorPolicyInterface> The current page of results.
|
|
|
|
* @return void
|
|
|
|
* @task policyimpl
|
|
|
|
*/
|
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
2012-04-14 19:13:29 +02:00
|
|
|
abstract protected function nextPage(array $page);
|
|
|
|
|
|
|
|
}
|