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
|
|
|
|
|
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();
|
|
|
|
*
|
2012-09-13 19:15:08 +02:00
|
|
|
* Normally, you should extend @{class:PhabricatorCursorPagedPolicyAwareQuery},
|
|
|
|
* not this class. @{class:PhabricatorCursorPagedPolicyAwareQuery} provides a
|
|
|
|
* more practical interface for building usable queries against most object
|
|
|
|
* types.
|
2012-08-08 21:15:58 +02:00
|
|
|
*
|
|
|
|
* 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
|
2012-09-13 19:15:08 +02:00
|
|
|
* with @{class:PhabricatorCursorPagedPolicyAwareQuery}, which can filter far
|
|
|
|
* more efficiently in MySQL.
|
2012-08-08 21:15:58 +02:00
|
|
|
*
|
|
|
|
* @task config Query Configuration
|
|
|
|
* @task exec Executing Queries
|
|
|
|
* @task policyimpl Policy Query Implementation
|
|
|
|
*/
|
2012-09-13 19:15:08 +02:00
|
|
|
abstract class PhabricatorPolicyAwareQuery 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;
|
2013-09-11 21:19:34 +02:00
|
|
|
private $parentQuery;
|
2012-08-08 21:15:58 +02:00
|
|
|
private $rawResultLimit;
|
2012-08-11 16:02:31 +02:00
|
|
|
private $capabilities;
|
Fix some file policy issues and add a "Query Workspace"
Summary:
Ref T603. Several issues here:
1. Currently, `FileQuery` does not actually respect object attachment edges when doing policy checks. Everything else works fine, but this was missing an `array_keys()`.
2. Once that's fixed, we hit a bunch of recursion issues. For example, when loading a User we load the profile picture, and then that loads the User, and that loads the profile picture, etc.
3. Introduce a "Query Workspace", which holds objects we know we've loaded and know we can see but haven't finished filtering and/or attaching data to. This allows subqueries to look up objects instead of querying for them.
- We can probably generalize this a bit to make a few other queries more efficient. Pholio currently has a similar (but less general) "mock cache". However, it's keyed by ID instead of PHID so it's not easy to reuse this right now.
This is a bit complex for the problem being solved, but I think it's the cleanest approach and I believe the primitive will be useful in the future.
Test Plan: Looked at pastes, macros, mocks and projects as a logged-in and logged-out user.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T603
Differential Revision: https://secure.phabricator.com/D7309
2013-10-14 23:36:06 +02:00
|
|
|
private $workspace = array();
|
2013-10-17 19:49:21 +02:00
|
|
|
private $policyFilteredPHIDs = array();
|
Lock policy queries to their applications
Summary:
While we mostly have reasonable effective object accessibility when you lock a user out of an application, it's primarily enforced at the controller level. Users can still, e.g., load the handles of objects they can't actually see. Instead, lock the queries to the applications so that you can, e.g., never load a revision if you don't have access to Differential.
This has several parts:
- For PolicyAware queries, provide an application class name method.
- If the query specifies a class name and the user doesn't have permission to use it, fail the entire query unconditionally.
- For handles, simplify query construction and count all the PHIDs as "restricted" so we get a UI full of "restricted" instead of "unknown" handles.
Test Plan:
- Added a unit test to verify I got all the class names right.
- Browsed around, logged in/out as a normal user with public policies on and off.
- Browsed around, logged in/out as a restricted user with public policies on and off. With restrictions, saw all traces of restricted apps removed or restricted.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Differential Revision: https://secure.phabricator.com/D7367
2013-10-22 02:20:27 +02:00
|
|
|
private $canUseApplication;
|
2012-08-08 21:15:58 +02:00
|
|
|
|
Fix an issue where file queries would throw incorrectly
Summary:
Ref T4589. When you look at a file, we load attached objects in order to run the "you can see this if you can see any attached object" policy check.
However, right now the subquery inherits the "throw on filter" flag from the parent query. This inheritance makes sense in other cases[1], but because this is an "ANY" rule it does not make sense here. In practice, it means that if the file is attached to several objects, and any of them gets filtered, you can not see the file.
Instead, explicitly drop the flag for this subquery.
[1] Sort of. It doesn't produce wrong results in other cases, but now that I think about it might produce a less-tailored error than it could. I'll look into this the next time I'm poking around.
Test Plan:
- Viewed an "All Users" file attached to a private Mock.
- Prior to this patch, I incorrectly received an exception when the Mock was loaded. This is wrong; I should be able to see the file because the policy is "All Users".
- After the patch, I can correctly view the file, just not the associated mock.
{F127074}
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: 20after4, aran, epriestley
Maniphest Tasks: T4589
Differential Revision: https://secure.phabricator.com/D8498
2014-08-02 23:46:36 +02:00
|
|
|
/**
|
|
|
|
* Should we continue or throw an exception when a query result is filtered
|
|
|
|
* by policy rules?
|
|
|
|
*
|
|
|
|
* Values are `true` (raise exceptions), `false` (do not raise exceptions)
|
|
|
|
* and `null` (inherit from parent query, with no exceptions by default).
|
|
|
|
*/
|
|
|
|
private $raisePolicyExceptions;
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-09-11 21:19:34 +02:00
|
|
|
/**
|
|
|
|
* Set the parent query of this query. This is useful for nested queries so
|
|
|
|
* that configuration like whether or not to raise policy exceptions is
|
|
|
|
* seamlessly passed along to child queries.
|
|
|
|
*
|
|
|
|
* @return this
|
|
|
|
* @task config
|
|
|
|
*/
|
|
|
|
final public function setParentQuery(PhabricatorPolicyAwareQuery $query) {
|
|
|
|
$this->parentQuery = $query;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the parent query. See @{method:setParentQuery} for discussion.
|
|
|
|
*
|
|
|
|
* @return PhabricatorPolicyAwareQuery The parent query.
|
|
|
|
* @task config
|
|
|
|
*/
|
|
|
|
final public function getParentQuery() {
|
|
|
|
return $this->parentQuery;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hook to configure whether this query should raise policy exceptions.
|
|
|
|
*
|
|
|
|
* @return this
|
|
|
|
* @task config
|
|
|
|
*/
|
|
|
|
final public function setRaisePolicyExceptions($bool) {
|
|
|
|
$this->raisePolicyExceptions = $bool;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
* @task config
|
|
|
|
*/
|
|
|
|
final public function shouldRaisePolicyExceptions() {
|
2013-12-06 02:00:53 +01:00
|
|
|
return (bool)$this->raisePolicyExceptions;
|
2013-09-11 21:19:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-08-11 16:02:31 +02:00
|
|
|
/**
|
|
|
|
* @task config
|
|
|
|
*/
|
|
|
|
final public function requireCapabilities(array $capabilities) {
|
|
|
|
$this->capabilities = $capabilities;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
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() {
|
|
|
|
|
2013-09-11 21:19:34 +02:00
|
|
|
$this->setRaisePolicyExceptions(true);
|
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
|
|
|
try {
|
|
|
|
$results = $this->execute();
|
|
|
|
} catch (Exception $ex) {
|
2013-09-11 21:19:34 +02:00
|
|
|
$this->setRaisePolicyExceptions(false);
|
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
|
|
|
throw $ex;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count($results) > 1) {
|
2014-06-09 20:36:49 +02:00
|
|
|
throw new Exception('Expected a single result!');
|
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
|
|
|
|
|
|
|
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) {
|
2014-06-09 20:36:49 +02:00
|
|
|
throw new Exception('Call setViewer() before execute()!');
|
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
|
|
|
}
|
|
|
|
|
2013-09-11 21:19:34 +02:00
|
|
|
$parent_query = $this->getParentQuery();
|
Fix an issue where file queries would throw incorrectly
Summary:
Ref T4589. When you look at a file, we load attached objects in order to run the "you can see this if you can see any attached object" policy check.
However, right now the subquery inherits the "throw on filter" flag from the parent query. This inheritance makes sense in other cases[1], but because this is an "ANY" rule it does not make sense here. In practice, it means that if the file is attached to several objects, and any of them gets filtered, you can not see the file.
Instead, explicitly drop the flag for this subquery.
[1] Sort of. It doesn't produce wrong results in other cases, but now that I think about it might produce a less-tailored error than it could. I'll look into this the next time I'm poking around.
Test Plan:
- Viewed an "All Users" file attached to a private Mock.
- Prior to this patch, I incorrectly received an exception when the Mock was loaded. This is wrong; I should be able to see the file because the policy is "All Users".
- After the patch, I can correctly view the file, just not the associated mock.
{F127074}
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: 20after4, aran, epriestley
Maniphest Tasks: T4589
Differential Revision: https://secure.phabricator.com/D8498
2014-08-02 23:46:36 +02:00
|
|
|
if ($parent_query && ($this->raisePolicyExceptions === null)) {
|
2013-09-11 21:19:34 +02:00
|
|
|
$this->setRaisePolicyExceptions(
|
|
|
|
$parent_query->shouldRaisePolicyExceptions());
|
|
|
|
}
|
|
|
|
|
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
|
|
|
$results = array();
|
|
|
|
|
2013-09-27 17:43:50 +02:00
|
|
|
$filter = $this->getPolicyFilter();
|
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
|
|
|
$offset = (int)$this->getOffset();
|
|
|
|
$limit = (int)$this->getLimit();
|
|
|
|
$count = 0;
|
|
|
|
|
2012-08-09 20:40:55 +02:00
|
|
|
if ($limit) {
|
|
|
|
$need = $offset + $limit;
|
|
|
|
} else {
|
|
|
|
$need = 0;
|
|
|
|
}
|
2012-08-08 21:15:58 +02:00
|
|
|
|
|
|
|
$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-09 03:55:07 +02:00
|
|
|
if ($need) {
|
|
|
|
$this->rawResultLimit = min($need - $count, 1024);
|
|
|
|
} else {
|
|
|
|
$this->rawResultLimit = 0;
|
2012-08-08 21:15:58 +02:00
|
|
|
}
|
|
|
|
|
Lock policy queries to their applications
Summary:
While we mostly have reasonable effective object accessibility when you lock a user out of an application, it's primarily enforced at the controller level. Users can still, e.g., load the handles of objects they can't actually see. Instead, lock the queries to the applications so that you can, e.g., never load a revision if you don't have access to Differential.
This has several parts:
- For PolicyAware queries, provide an application class name method.
- If the query specifies a class name and the user doesn't have permission to use it, fail the entire query unconditionally.
- For handles, simplify query construction and count all the PHIDs as "restricted" so we get a UI full of "restricted" instead of "unknown" handles.
Test Plan:
- Added a unit test to verify I got all the class names right.
- Browsed around, logged in/out as a normal user with public policies on and off.
- Browsed around, logged in/out as a restricted user with public policies on and off. With restrictions, saw all traces of restricted apps removed or restricted.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Differential Revision: https://secure.phabricator.com/D7367
2013-10-22 02:20:27 +02:00
|
|
|
if ($this->canViewerUseQueryApplication()) {
|
|
|
|
try {
|
|
|
|
$page = $this->loadPage();
|
|
|
|
} catch (PhabricatorEmptyQueryException $ex) {
|
|
|
|
$page = array();
|
|
|
|
}
|
|
|
|
} else {
|
2013-03-01 20:28:02 +01:00
|
|
|
$page = array();
|
|
|
|
}
|
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
|
|
|
|
2013-07-21 18:27:00 +02:00
|
|
|
if ($page) {
|
2013-07-22 21:20:31 +02:00
|
|
|
$maybe_visible = $this->willFilterPage($page);
|
2013-07-21 18:27:00 +02:00
|
|
|
} else {
|
2013-07-22 21:20:31 +02:00
|
|
|
$maybe_visible = array();
|
2013-07-21 18:27:00 +02:00
|
|
|
}
|
|
|
|
|
2013-09-10 18:06:20 +02:00
|
|
|
if ($this->shouldDisablePolicyFiltering()) {
|
|
|
|
$visible = $maybe_visible;
|
|
|
|
} else {
|
|
|
|
$visible = $filter->apply($maybe_visible);
|
2013-10-17 19:49:21 +02:00
|
|
|
|
|
|
|
$policy_filtered = array();
|
|
|
|
foreach ($maybe_visible as $key => $object) {
|
|
|
|
if (empty($visible[$key])) {
|
|
|
|
$phid = $object->getPHID();
|
|
|
|
if ($phid) {
|
|
|
|
$policy_filtered[$phid] = $phid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$this->addPolicyFilteredPHIDs($policy_filtered);
|
2013-09-10 18:06:20 +02:00
|
|
|
}
|
2013-07-22 21:20:31 +02:00
|
|
|
|
Fix some file policy issues and add a "Query Workspace"
Summary:
Ref T603. Several issues here:
1. Currently, `FileQuery` does not actually respect object attachment edges when doing policy checks. Everything else works fine, but this was missing an `array_keys()`.
2. Once that's fixed, we hit a bunch of recursion issues. For example, when loading a User we load the profile picture, and then that loads the User, and that loads the profile picture, etc.
3. Introduce a "Query Workspace", which holds objects we know we've loaded and know we can see but haven't finished filtering and/or attaching data to. This allows subqueries to look up objects instead of querying for them.
- We can probably generalize this a bit to make a few other queries more efficient. Pholio currently has a similar (but less general) "mock cache". However, it's keyed by ID instead of PHID so it's not easy to reuse this right now.
This is a bit complex for the problem being solved, but I think it's the cleanest approach and I believe the primitive will be useful in the future.
Test Plan: Looked at pastes, macros, mocks and projects as a logged-in and logged-out user.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T603
Differential Revision: https://secure.phabricator.com/D7309
2013-10-14 23:36:06 +02:00
|
|
|
if ($visible) {
|
|
|
|
$this->putObjectsInWorkspace($this->getWorkspaceMapForPage($visible));
|
|
|
|
$visible = $this->didFilterPage($visible);
|
|
|
|
}
|
|
|
|
|
2013-07-22 21:20:31 +02:00
|
|
|
$removed = array();
|
|
|
|
foreach ($maybe_visible as $key => $object) {
|
|
|
|
if (empty($visible[$key])) {
|
|
|
|
$removed[$key] = $object;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->didFilterResults($removed);
|
|
|
|
|
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
|
|
|
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-09 03:55:07 +02:00
|
|
|
if (!$this->rawResultLimit) {
|
2012-08-08 21:15:58 +02:00
|
|
|
// If we don't have a load count, we loaded all the results. We do
|
|
|
|
// not need to load another page.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2012-08-09 03:55:07 +02:00
|
|
|
if (count($page) < $this->rawResultLimit) {
|
2012-08-08 21:15:58 +02:00
|
|
|
// 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);
|
|
|
|
|
Allow policy-aware queries to prefilter results
Summary:
Provides a simple way for policy-aware queries to pre-filter results without needing to maintain separate cursors, and fixes a bunch of filter-related edge cases.
- For reverse-paged cursor queries, we previously reversed each individual set of results. If the final result set is built out of multiple pages, it's in the wrong order overall, with each page in the correct order in sequence. Instead, reverse everything at the end. This also simplifies construction of queries.
- `AphrontCursorPagerView` would always render a "<< First" link when paging backward, even if we were on the first page of results.
- Add a filtering hook to let queries perform in-application pre-policy filtering as simply as possible (i.e., without maintaing their own cursors over the result sets).
Test Plan: Made feed randomly prefilter half the results, and paged forward and backward. Observed correct result ordering, pagination, and next/previous links.
Reviewers: btrahan, vrana
Reviewed By: btrahan
CC: aran
Differential Revision: https://secure.phabricator.com/D3787
2012-10-23 21:01:11 +02:00
|
|
|
$results = $this->didLoadResults($results);
|
|
|
|
|
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 $results;
|
|
|
|
}
|
|
|
|
|
2013-09-27 17:43:50 +02:00
|
|
|
private function getPolicyFilter() {
|
|
|
|
$filter = new PhabricatorPolicyFilter();
|
|
|
|
$filter->setViewer($this->viewer);
|
2014-05-19 21:41:30 +02:00
|
|
|
$capabilities = $this->getRequiredCapabilities();
|
2013-09-27 17:43:50 +02:00
|
|
|
$filter->requireCapabilities($capabilities);
|
|
|
|
$filter->raisePolicyExceptions($this->shouldRaisePolicyExceptions());
|
|
|
|
|
|
|
|
return $filter;
|
2014-05-19 21:41:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
protected function getRequiredCapabilities() {
|
|
|
|
if ($this->capabilities) {
|
|
|
|
return $this->capabilities;
|
|
|
|
}
|
|
|
|
|
|
|
|
return array(
|
|
|
|
PhabricatorPolicyCapability::CAN_VIEW,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function applyPolicyFilter(array $objects, array $capabilities) {
|
|
|
|
if ($this->shouldDisablePolicyFiltering()) {
|
|
|
|
return $objects;
|
|
|
|
}
|
|
|
|
$filter = $this->getPolicyFilter();
|
|
|
|
$filter->requireCapabilities($capabilities);
|
|
|
|
return $filter->apply($objects);
|
2013-09-27 17:43:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
protected function didRejectResult(PhabricatorPolicyInterface $object) {
|
|
|
|
$this->getPolicyFilter()->rejectObject(
|
|
|
|
$object,
|
|
|
|
$object->getPolicy(PhabricatorPolicyCapability::CAN_VIEW),
|
|
|
|
PhabricatorPolicyCapability::CAN_VIEW);
|
|
|
|
}
|
|
|
|
|
Lock policy queries to their applications
Summary:
While we mostly have reasonable effective object accessibility when you lock a user out of an application, it's primarily enforced at the controller level. Users can still, e.g., load the handles of objects they can't actually see. Instead, lock the queries to the applications so that you can, e.g., never load a revision if you don't have access to Differential.
This has several parts:
- For PolicyAware queries, provide an application class name method.
- If the query specifies a class name and the user doesn't have permission to use it, fail the entire query unconditionally.
- For handles, simplify query construction and count all the PHIDs as "restricted" so we get a UI full of "restricted" instead of "unknown" handles.
Test Plan:
- Added a unit test to verify I got all the class names right.
- Browsed around, logged in/out as a normal user with public policies on and off.
- Browsed around, logged in/out as a restricted user with public policies on and off. With restrictions, saw all traces of restricted apps removed or restricted.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Differential Revision: https://secure.phabricator.com/D7367
2013-10-22 02:20:27 +02:00
|
|
|
public function addPolicyFilteredPHIDs(array $phids) {
|
2013-10-17 19:49:21 +02:00
|
|
|
$this->policyFilteredPHIDs += $phids;
|
|
|
|
if ($this->getParentQuery()) {
|
|
|
|
$this->getParentQuery()->addPolicyFilteredPHIDs($phids);
|
|
|
|
}
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return a map of all object PHIDs which were loaded in the query but
|
|
|
|
* filtered out by policy constraints. This allows a caller to distinguish
|
|
|
|
* between objects which do not exist (or, at least, were filtered at the
|
|
|
|
* content level) and objects which exist but aren't visible.
|
|
|
|
*
|
|
|
|
* @return map<phid, phid> Map of object PHIDs which were filtered
|
|
|
|
* by policies.
|
|
|
|
* @task exec
|
|
|
|
*/
|
|
|
|
public function getPolicyFilteredPHIDs() {
|
|
|
|
return $this->policyFilteredPHIDs;
|
|
|
|
}
|
|
|
|
|
2012-08-08 21:15:58 +02:00
|
|
|
|
Fix some file policy issues and add a "Query Workspace"
Summary:
Ref T603. Several issues here:
1. Currently, `FileQuery` does not actually respect object attachment edges when doing policy checks. Everything else works fine, but this was missing an `array_keys()`.
2. Once that's fixed, we hit a bunch of recursion issues. For example, when loading a User we load the profile picture, and then that loads the User, and that loads the profile picture, etc.
3. Introduce a "Query Workspace", which holds objects we know we've loaded and know we can see but haven't finished filtering and/or attaching data to. This allows subqueries to look up objects instead of querying for them.
- We can probably generalize this a bit to make a few other queries more efficient. Pholio currently has a similar (but less general) "mock cache". However, it's keyed by ID instead of PHID so it's not easy to reuse this right now.
This is a bit complex for the problem being solved, but I think it's the cleanest approach and I believe the primitive will be useful in the future.
Test Plan: Looked at pastes, macros, mocks and projects as a logged-in and logged-out user.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T603
Differential Revision: https://secure.phabricator.com/D7309
2013-10-14 23:36:06 +02:00
|
|
|
/* -( Query Workspace )---------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Put a map of objects into the query workspace. Many queries perform
|
|
|
|
* subqueries, which can eventually end up loading the same objects more than
|
|
|
|
* once (often to perform policy checks).
|
|
|
|
*
|
|
|
|
* For example, loading a user may load the user's profile image, which might
|
|
|
|
* load the user object again in order to verify that the viewer has
|
|
|
|
* permission to see the file.
|
|
|
|
*
|
|
|
|
* The "query workspace" allows queries to load objects from elsewhere in a
|
|
|
|
* query block instead of refetching them.
|
|
|
|
*
|
|
|
|
* When using the query workspace, it's important to obey two rules:
|
|
|
|
*
|
|
|
|
* **Never put objects into the workspace which the viewer may not be able
|
|
|
|
* to see**. You need to apply all policy filtering //before// putting
|
|
|
|
* objects in the workspace. Otherwise, subqueries may read the objects and
|
|
|
|
* use them to permit access to content the user shouldn't be able to view.
|
|
|
|
*
|
|
|
|
* **Fully enrich objects pulled from the workspace.** After pulling objects
|
|
|
|
* from the workspace, you still need to load and attach any additional
|
|
|
|
* content the query requests. Otherwise, a query might return objects without
|
|
|
|
* requested content.
|
|
|
|
*
|
|
|
|
* Generally, you do not need to update the workspace yourself: it is
|
|
|
|
* automatically populated as a side effect of objects surviving policy
|
|
|
|
* filtering.
|
|
|
|
*
|
|
|
|
* @param map<phid, PhabricatorPolicyInterface> Objects to add to the query
|
|
|
|
* workspace.
|
|
|
|
* @return this
|
|
|
|
* @task workspace
|
|
|
|
*/
|
|
|
|
public function putObjectsInWorkspace(array $objects) {
|
|
|
|
assert_instances_of($objects, 'PhabricatorPolicyInterface');
|
|
|
|
|
|
|
|
$viewer_phid = $this->getViewer()->getPHID();
|
|
|
|
|
|
|
|
// The workspace is scoped per viewer to prevent accidental contamination.
|
|
|
|
if (empty($this->workspace[$viewer_phid])) {
|
|
|
|
$this->workspace[$viewer_phid] = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->workspace[$viewer_phid] += $objects;
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve objects from the query workspace. For more discussion about the
|
|
|
|
* workspace mechanism, see @{method:putObjectsInWorkspace}. This method
|
|
|
|
* searches both the current query's workspace and the workspaces of parent
|
|
|
|
* queries.
|
|
|
|
*
|
2014-07-12 16:45:33 +02:00
|
|
|
* @param list<phid> List of PHIDs to retrieve.
|
Fix some file policy issues and add a "Query Workspace"
Summary:
Ref T603. Several issues here:
1. Currently, `FileQuery` does not actually respect object attachment edges when doing policy checks. Everything else works fine, but this was missing an `array_keys()`.
2. Once that's fixed, we hit a bunch of recursion issues. For example, when loading a User we load the profile picture, and then that loads the User, and that loads the profile picture, etc.
3. Introduce a "Query Workspace", which holds objects we know we've loaded and know we can see but haven't finished filtering and/or attaching data to. This allows subqueries to look up objects instead of querying for them.
- We can probably generalize this a bit to make a few other queries more efficient. Pholio currently has a similar (but less general) "mock cache". However, it's keyed by ID instead of PHID so it's not easy to reuse this right now.
This is a bit complex for the problem being solved, but I think it's the cleanest approach and I believe the primitive will be useful in the future.
Test Plan: Looked at pastes, macros, mocks and projects as a logged-in and logged-out user.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T603
Differential Revision: https://secure.phabricator.com/D7309
2013-10-14 23:36:06 +02:00
|
|
|
* @return this
|
|
|
|
* @task workspace
|
|
|
|
*/
|
|
|
|
public function getObjectsFromWorkspace(array $phids) {
|
|
|
|
$viewer_phid = $this->getViewer()->getPHID();
|
|
|
|
|
|
|
|
$results = array();
|
|
|
|
foreach ($phids as $key => $phid) {
|
|
|
|
if (isset($this->workspace[$viewer_phid][$phid])) {
|
|
|
|
$results[$phid] = $this->workspace[$viewer_phid][$phid];
|
|
|
|
unset($phids[$key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($phids && $this->getParentQuery()) {
|
|
|
|
$results += $this->getParentQuery()->getObjectsFromWorkspace($phids);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $results;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert a result page to a `<phid, PhabricatorPolicyInterface>` map.
|
|
|
|
*
|
|
|
|
* @param list<PhabricatorPolicyInterface> Objects.
|
|
|
|
* @return map<phid, PhabricatorPolicyInterface> Map of objects which can
|
|
|
|
* be put into the workspace.
|
|
|
|
* @task workspace
|
|
|
|
*/
|
|
|
|
protected function getWorkspaceMapForPage(array $results) {
|
|
|
|
$map = array();
|
|
|
|
foreach ($results as $result) {
|
|
|
|
$phid = $result->getPHID();
|
|
|
|
if ($phid !== null) {
|
|
|
|
$map[$phid] = $result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $map;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
Allow policy-aware queries to prefilter results
Summary:
Provides a simple way for policy-aware queries to pre-filter results without needing to maintain separate cursors, and fixes a bunch of filter-related edge cases.
- For reverse-paged cursor queries, we previously reversed each individual set of results. If the final result set is built out of multiple pages, it's in the wrong order overall, with each page in the correct order in sequence. Instead, reverse everything at the end. This also simplifies construction of queries.
- `AphrontCursorPagerView` would always render a "<< First" link when paging backward, even if we were on the first page of results.
- Add a filtering hook to let queries perform in-application pre-policy filtering as simply as possible (i.e., without maintaing their own cursors over the result sets).
Test Plan: Made feed randomly prefilter half the results, and paged forward and backward. Observed correct result ordering, pagination, and next/previous links.
Reviewers: btrahan, vrana
Reviewed By: btrahan
CC: aran
Differential Revision: https://secure.phabricator.com/D3787
2012-10-23 21:01:11 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Hook for applying a page filter prior to the privacy filter. This allows
|
|
|
|
* you to drop some items from the result set without creating problems with
|
Fix some file policy issues and add a "Query Workspace"
Summary:
Ref T603. Several issues here:
1. Currently, `FileQuery` does not actually respect object attachment edges when doing policy checks. Everything else works fine, but this was missing an `array_keys()`.
2. Once that's fixed, we hit a bunch of recursion issues. For example, when loading a User we load the profile picture, and then that loads the User, and that loads the profile picture, etc.
3. Introduce a "Query Workspace", which holds objects we know we've loaded and know we can see but haven't finished filtering and/or attaching data to. This allows subqueries to look up objects instead of querying for them.
- We can probably generalize this a bit to make a few other queries more efficient. Pholio currently has a similar (but less general) "mock cache". However, it's keyed by ID instead of PHID so it's not easy to reuse this right now.
This is a bit complex for the problem being solved, but I think it's the cleanest approach and I believe the primitive will be useful in the future.
Test Plan: Looked at pastes, macros, mocks and projects as a logged-in and logged-out user.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T603
Differential Revision: https://secure.phabricator.com/D7309
2013-10-14 23:36:06 +02:00
|
|
|
* pagination or cursor updates. You can also load and attach data which is
|
|
|
|
* required to perform policy filtering.
|
|
|
|
*
|
|
|
|
* Generally, you should load non-policy data and perform non-policy filtering
|
|
|
|
* later, in @{method:didFilterPage}. Strictly fewer objects will make it that
|
|
|
|
* far (so the program will load less data) and subqueries from that context
|
|
|
|
* can use the query workspace to further reduce query load.
|
Allow policy-aware queries to prefilter results
Summary:
Provides a simple way for policy-aware queries to pre-filter results without needing to maintain separate cursors, and fixes a bunch of filter-related edge cases.
- For reverse-paged cursor queries, we previously reversed each individual set of results. If the final result set is built out of multiple pages, it's in the wrong order overall, with each page in the correct order in sequence. Instead, reverse everything at the end. This also simplifies construction of queries.
- `AphrontCursorPagerView` would always render a "<< First" link when paging backward, even if we were on the first page of results.
- Add a filtering hook to let queries perform in-application pre-policy filtering as simply as possible (i.e., without maintaing their own cursors over the result sets).
Test Plan: Made feed randomly prefilter half the results, and paged forward and backward. Observed correct result ordering, pagination, and next/previous links.
Reviewers: btrahan, vrana
Reviewed By: btrahan
CC: aran
Differential Revision: https://secure.phabricator.com/D3787
2012-10-23 21:01:11 +02:00
|
|
|
*
|
2013-07-21 18:27:00 +02:00
|
|
|
* This method will only be called if data is available. Implementations
|
|
|
|
* do not need to handle the case of no results specially.
|
|
|
|
*
|
Allow policy-aware queries to prefilter results
Summary:
Provides a simple way for policy-aware queries to pre-filter results without needing to maintain separate cursors, and fixes a bunch of filter-related edge cases.
- For reverse-paged cursor queries, we previously reversed each individual set of results. If the final result set is built out of multiple pages, it's in the wrong order overall, with each page in the correct order in sequence. Instead, reverse everything at the end. This also simplifies construction of queries.
- `AphrontCursorPagerView` would always render a "<< First" link when paging backward, even if we were on the first page of results.
- Add a filtering hook to let queries perform in-application pre-policy filtering as simply as possible (i.e., without maintaing their own cursors over the result sets).
Test Plan: Made feed randomly prefilter half the results, and paged forward and backward. Observed correct result ordering, pagination, and next/previous links.
Reviewers: btrahan, vrana
Reviewed By: btrahan
CC: aran
Differential Revision: https://secure.phabricator.com/D3787
2012-10-23 21:01:11 +02:00
|
|
|
* @param list<wild> Results from `loadPage()`.
|
|
|
|
* @return list<PhabricatorPolicyInterface> Objects for policy filtering.
|
|
|
|
* @task policyimpl
|
|
|
|
*/
|
|
|
|
protected function willFilterPage(array $page) {
|
|
|
|
return $page;
|
|
|
|
}
|
|
|
|
|
Fix some file policy issues and add a "Query Workspace"
Summary:
Ref T603. Several issues here:
1. Currently, `FileQuery` does not actually respect object attachment edges when doing policy checks. Everything else works fine, but this was missing an `array_keys()`.
2. Once that's fixed, we hit a bunch of recursion issues. For example, when loading a User we load the profile picture, and then that loads the User, and that loads the profile picture, etc.
3. Introduce a "Query Workspace", which holds objects we know we've loaded and know we can see but haven't finished filtering and/or attaching data to. This allows subqueries to look up objects instead of querying for them.
- We can probably generalize this a bit to make a few other queries more efficient. Pholio currently has a similar (but less general) "mock cache". However, it's keyed by ID instead of PHID so it's not easy to reuse this right now.
This is a bit complex for the problem being solved, but I think it's the cleanest approach and I believe the primitive will be useful in the future.
Test Plan: Looked at pastes, macros, mocks and projects as a logged-in and logged-out user.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T603
Differential Revision: https://secure.phabricator.com/D7309
2013-10-14 23:36:06 +02:00
|
|
|
/**
|
|
|
|
* Hook for performing additional non-policy loading or filtering after an
|
|
|
|
* object has satisfied all policy checks. Generally, this means loading and
|
|
|
|
* attaching related data.
|
|
|
|
*
|
|
|
|
* Subqueries executed during this phase can use the query workspace, which
|
|
|
|
* may improve performance or make circular policies resolvable. Data which
|
|
|
|
* is not necessary for policy filtering should generally be loaded here.
|
|
|
|
*
|
|
|
|
* This callback can still filter objects (for example, if attachable data
|
|
|
|
* is discovered to not exist), but should not do so for policy reasons.
|
|
|
|
*
|
|
|
|
* This method will only be called if data is available. Implementations do
|
|
|
|
* not need to handle the case of no results specially.
|
|
|
|
*
|
|
|
|
* @param list<wild> Results from @{method:willFilterPage()}.
|
|
|
|
* @return list<PhabricatorPolicyInterface> Objects after additional
|
|
|
|
* non-policy processing.
|
|
|
|
*/
|
|
|
|
protected function didFilterPage(array $page) {
|
|
|
|
return $page;
|
|
|
|
}
|
|
|
|
|
Allow policy-aware queries to prefilter results
Summary:
Provides a simple way for policy-aware queries to pre-filter results without needing to maintain separate cursors, and fixes a bunch of filter-related edge cases.
- For reverse-paged cursor queries, we previously reversed each individual set of results. If the final result set is built out of multiple pages, it's in the wrong order overall, with each page in the correct order in sequence. Instead, reverse everything at the end. This also simplifies construction of queries.
- `AphrontCursorPagerView` would always render a "<< First" link when paging backward, even if we were on the first page of results.
- Add a filtering hook to let queries perform in-application pre-policy filtering as simply as possible (i.e., without maintaing their own cursors over the result sets).
Test Plan: Made feed randomly prefilter half the results, and paged forward and backward. Observed correct result ordering, pagination, and next/previous links.
Reviewers: btrahan, vrana
Reviewed By: btrahan
CC: aran
Differential Revision: https://secure.phabricator.com/D3787
2012-10-23 21:01:11 +02:00
|
|
|
|
2013-07-22 21:20:31 +02:00
|
|
|
/**
|
|
|
|
* Hook for removing filtered results from alternate result sets. This
|
|
|
|
* hook will be called with any objects which were returned by the query but
|
|
|
|
* filtered for policy reasons. The query should remove them from any cached
|
|
|
|
* or partial result sets.
|
|
|
|
*
|
|
|
|
* @param list<wild> List of objects that should not be returned by alternate
|
|
|
|
* result mechanisms.
|
|
|
|
* @return void
|
|
|
|
* @task policyimpl
|
|
|
|
*/
|
|
|
|
protected function didFilterResults(array $results) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
Allow policy-aware queries to prefilter results
Summary:
Provides a simple way for policy-aware queries to pre-filter results without needing to maintain separate cursors, and fixes a bunch of filter-related edge cases.
- For reverse-paged cursor queries, we previously reversed each individual set of results. If the final result set is built out of multiple pages, it's in the wrong order overall, with each page in the correct order in sequence. Instead, reverse everything at the end. This also simplifies construction of queries.
- `AphrontCursorPagerView` would always render a "<< First" link when paging backward, even if we were on the first page of results.
- Add a filtering hook to let queries perform in-application pre-policy filtering as simply as possible (i.e., without maintaing their own cursors over the result sets).
Test Plan: Made feed randomly prefilter half the results, and paged forward and backward. Observed correct result ordering, pagination, and next/previous links.
Reviewers: btrahan, vrana
Reviewed By: btrahan
CC: aran
Differential Revision: https://secure.phabricator.com/D3787
2012-10-23 21:01:11 +02:00
|
|
|
/**
|
|
|
|
* Hook for applying final adjustments before results are returned. This is
|
|
|
|
* used by @{class:PhabricatorCursorPagedPolicyAwareQuery} to reverse results
|
|
|
|
* that are queried during reverse paging.
|
|
|
|
*
|
|
|
|
* @param list<PhabricatorPolicyInterface> Query results.
|
|
|
|
* @return list<PhabricatorPolicyInterface> Final results.
|
|
|
|
* @task policyimpl
|
|
|
|
*/
|
|
|
|
protected function didLoadResults(array $results) {
|
|
|
|
return $results;
|
|
|
|
}
|
|
|
|
|
2013-09-10 18:06:20 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Allows a subclass to disable policy filtering. This method is dangerous.
|
|
|
|
* It should be used only if the query loads data which has already been
|
|
|
|
* filtered (for example, because it wraps some other query which uses
|
|
|
|
* normal policy filtering).
|
|
|
|
*
|
|
|
|
* @return bool True to disable all policy filtering.
|
|
|
|
* @task policyimpl
|
|
|
|
*/
|
|
|
|
protected function shouldDisablePolicyFiltering() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
Lock policy queries to their applications
Summary:
While we mostly have reasonable effective object accessibility when you lock a user out of an application, it's primarily enforced at the controller level. Users can still, e.g., load the handles of objects they can't actually see. Instead, lock the queries to the applications so that you can, e.g., never load a revision if you don't have access to Differential.
This has several parts:
- For PolicyAware queries, provide an application class name method.
- If the query specifies a class name and the user doesn't have permission to use it, fail the entire query unconditionally.
- For handles, simplify query construction and count all the PHIDs as "restricted" so we get a UI full of "restricted" instead of "unknown" handles.
Test Plan:
- Added a unit test to verify I got all the class names right.
- Browsed around, logged in/out as a normal user with public policies on and off.
- Browsed around, logged in/out as a restricted user with public policies on and off. With restrictions, saw all traces of restricted apps removed or restricted.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Differential Revision: https://secure.phabricator.com/D7367
2013-10-22 02:20:27 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* If this query belongs to an application, return the application class name
|
|
|
|
* here. This will prevent the query from returning results if the viewer can
|
|
|
|
* not access the application.
|
|
|
|
*
|
|
|
|
* If this query does not belong to an application, return `null`.
|
|
|
|
*
|
|
|
|
* @return string|null Application class name.
|
|
|
|
*/
|
|
|
|
abstract public function getQueryApplicationClass();
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determine if the viewer has permission to use this query's application.
|
|
|
|
* For queries which aren't part of an application, this method always returns
|
|
|
|
* true.
|
|
|
|
*
|
|
|
|
* @return bool True if the viewer has application-level permission to
|
|
|
|
* execute the query.
|
|
|
|
*/
|
|
|
|
public function canViewerUseQueryApplication() {
|
|
|
|
if ($this->canUseApplication === null) {
|
|
|
|
$class = $this->getQueryApplicationClass();
|
|
|
|
if (!$class) {
|
|
|
|
$this->canUseApplication = true;
|
|
|
|
} else {
|
|
|
|
$result = id(new PhabricatorApplicationQuery())
|
|
|
|
->setViewer($this->getViewer())
|
|
|
|
->withClasses(array($class))
|
|
|
|
->execute();
|
|
|
|
|
|
|
|
$this->canUseApplication = (bool)$result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->canUseApplication;
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|