2011-06-10 08:53:53 +02:00
|
|
|
<?php
|
|
|
|
|
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 class PhabricatorPaste extends PhabricatorPasteDAO
|
2013-08-06 02:11:46 +02:00
|
|
|
implements
|
|
|
|
PhabricatorSubscribableInterface,
|
|
|
|
PhabricatorTokenReceiverInterface,
|
2013-10-25 21:52:00 +02:00
|
|
|
PhabricatorFlaggableInterface,
|
2014-09-09 23:21:13 +02:00
|
|
|
PhabricatorMentionableInterface,
|
2014-06-04 02:22:09 +02:00
|
|
|
PhabricatorPolicyInterface,
|
2014-09-25 22:42:38 +02:00
|
|
|
PhabricatorProjectInterface,
|
|
|
|
PhabricatorDestructibleInterface,
|
2015-06-05 02:45:24 +02:00
|
|
|
PhabricatorApplicationTransactionInterface,
|
|
|
|
PhabricatorSpacesInterface {
|
2011-06-10 08:53:53 +02:00
|
|
|
|
|
|
|
protected $title;
|
|
|
|
protected $authorPHID;
|
|
|
|
protected $filePHID;
|
2011-07-04 00:29:58 +02:00
|
|
|
protected $language;
|
2011-07-15 22:49:45 +02:00
|
|
|
protected $parentPHID;
|
2012-09-13 19:11:14 +02:00
|
|
|
protected $viewPolicy;
|
2014-12-31 17:23:47 +01:00
|
|
|
protected $editPolicy;
|
2013-08-06 02:11:46 +02:00
|
|
|
protected $mailKey;
|
2015-06-05 02:45:24 +02:00
|
|
|
protected $spacePHID;
|
2011-06-10 08:53:53 +02:00
|
|
|
|
2013-09-03 15:02:14 +02:00
|
|
|
private $content = self::ATTACHABLE;
|
|
|
|
private $rawContent = self::ATTACHABLE;
|
2012-08-24 22:20:20 +02:00
|
|
|
|
2013-10-16 19:35:52 +02:00
|
|
|
public static function initializeNewPaste(PhabricatorUser $actor) {
|
|
|
|
$app = id(new PhabricatorApplicationQuery())
|
|
|
|
->setViewer($actor)
|
2014-07-23 02:03:09 +02:00
|
|
|
->withClasses(array('PhabricatorPasteApplication'))
|
2013-10-16 19:35:52 +02:00
|
|
|
->executeOne();
|
|
|
|
|
2014-07-25 00:20:39 +02:00
|
|
|
$view_policy = $app->getPolicy(PasteDefaultViewCapability::CAPABILITY);
|
2014-12-31 17:23:47 +01:00
|
|
|
$edit_policy = $app->getPolicy(PasteDefaultEditCapability::CAPABILITY);
|
2013-10-16 19:35:52 +02:00
|
|
|
|
|
|
|
return id(new PhabricatorPaste())
|
|
|
|
->setTitle('')
|
|
|
|
->setAuthorPHID($actor->getPHID())
|
2014-12-31 17:23:47 +01:00
|
|
|
->setViewPolicy($view_policy)
|
|
|
|
->setEditPolicy($edit_policy);
|
2013-10-16 19:35:52 +02:00
|
|
|
}
|
|
|
|
|
2012-08-24 22:19:14 +02:00
|
|
|
public function getURI() {
|
2015-06-05 19:42:49 +02:00
|
|
|
return '/'.$this->getMonogram();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getMonogram() {
|
|
|
|
return 'P'.$this->getID();
|
2012-08-24 22:19:14 +02:00
|
|
|
}
|
|
|
|
|
2015-01-13 20:47:05 +01:00
|
|
|
protected function getConfiguration() {
|
2011-06-10 08:53:53 +02:00
|
|
|
return array(
|
|
|
|
self::CONFIG_AUX_PHID => true,
|
2014-09-24 22:50:57 +02:00
|
|
|
self::CONFIG_COLUMN_SCHEMA => array(
|
|
|
|
'title' => 'text255',
|
|
|
|
'language' => 'text64',
|
|
|
|
'mailKey' => 'bytes20',
|
|
|
|
'parentPHID' => 'phid?',
|
2014-10-01 16:59:44 +02:00
|
|
|
|
|
|
|
// T6203/NULLABILITY
|
|
|
|
// Pastes should always have a view policy.
|
|
|
|
'viewPolicy' => 'policy?',
|
2014-09-24 22:50:57 +02:00
|
|
|
),
|
|
|
|
self::CONFIG_KEY_SCHEMA => array(
|
|
|
|
'parentPHID' => array(
|
|
|
|
'columns' => array('parentPHID'),
|
|
|
|
),
|
|
|
|
'authorPHID' => array(
|
|
|
|
'columns' => array('authorPHID'),
|
|
|
|
),
|
|
|
|
'key_dateCreated' => array(
|
|
|
|
'columns' => array('dateCreated'),
|
|
|
|
),
|
|
|
|
'key_language' => array(
|
|
|
|
'columns' => array('language'),
|
|
|
|
),
|
|
|
|
),
|
2011-06-10 08:53:53 +02:00
|
|
|
) + parent::getConfiguration();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function generatePHID() {
|
|
|
|
return PhabricatorPHID::generateNewPHID(
|
2014-07-24 00:05:46 +02:00
|
|
|
PhabricatorPastePastePHIDType::TYPECONST);
|
2011-06-10 08:53:53 +02:00
|
|
|
}
|
|
|
|
|
2013-08-06 02:11:46 +02:00
|
|
|
public function save() {
|
|
|
|
if (!$this->getMailKey()) {
|
|
|
|
$this->setMailKey(Filesystem::readRandomCharacters(20));
|
|
|
|
}
|
|
|
|
return parent::save();
|
|
|
|
}
|
|
|
|
|
2012-08-15 22:45:53 +02:00
|
|
|
public function getFullName() {
|
|
|
|
$title = $this->getTitle();
|
|
|
|
if (!$title) {
|
2013-02-13 20:47:31 +01:00
|
|
|
$title = pht('(An Untitled Masterwork)');
|
2012-08-15 22:45:53 +02:00
|
|
|
}
|
|
|
|
return 'P'.$this->getID().' '.$title;
|
|
|
|
}
|
|
|
|
|
2012-08-24 22:20:20 +02:00
|
|
|
public function getContent() {
|
2013-09-03 15:02:14 +02:00
|
|
|
return $this->assertAttached($this->content);
|
2012-08-24 22:20:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function attachContent($content) {
|
|
|
|
$this->content = $content;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-12-17 01:33:42 +01:00
|
|
|
public function getRawContent() {
|
2013-09-03 15:02:14 +02:00
|
|
|
return $this->assertAttached($this->rawContent);
|
2012-12-17 01:33:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function attachRawContent($raw_content) {
|
|
|
|
$this->rawContent = $raw_content;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-10-16 19:35:52 +02:00
|
|
|
/* -( PhabricatorSubscribableInterface )----------------------------------- */
|
2013-08-06 02:11:46 +02:00
|
|
|
|
|
|
|
|
|
|
|
public function isAutomaticallySubscribed($phid) {
|
|
|
|
return ($this->authorPHID == $phid);
|
|
|
|
}
|
|
|
|
|
Make Projects a PhabricatorSubscribableInterface, but with restricted defaults
Summary:
Ref T4379. I want project subscriptions to work like this (yell if this seems whacky, since it makes subscriptions mean somethign a little different for projects than they do for other objects):
- You can only subscribe to a project if you're a project member.
- When you're added as a member, you're added as a subscriber.
- When you're removed as a member, you're removed as a subscriber.
- While you're a member, you can optionally unsubscribe.
From a UI perspective:
- We don't show the subscriber list, since it's going to be some uninteresting subset of the member list.
- We don't show CC transactions in history, since they're an uninteresting near-approximation of the membership transactions.
- You only see the subscription controls if you're a member.
To do this, I've augmented `PhabricatorSubscribableInterface` with two new methods. It would be nice if we were on PHP 5.4+ and could just use traits for this, but we should get data about version usage before we think about this. For now, copy/paste the default implementations into every implementing class.
Then, I implemented the interface in `PhabricatorProject` but with alternate defaults.
Test Plan:
- Used the normal interaction on existing objects.
- This has no actual effect on projects, verified no subscription stuff mysteriously appeared.
- Hit the new error case by fiddling with the UI.
Reviewers: btrahan
Reviewed By: btrahan
CC: chad, aran
Maniphest Tasks: T4379
Differential Revision: https://secure.phabricator.com/D8165
2014-02-10 23:29:17 +01:00
|
|
|
public function shouldShowSubscribersProperty() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function shouldAllowSubscription($phid) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-08-06 02:11:46 +02:00
|
|
|
|
2013-05-09 23:21:21 +02:00
|
|
|
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
|
|
|
|
|
|
|
|
public function getUsersToNotifyOfTokenGiven() {
|
|
|
|
return array(
|
|
|
|
$this->getAuthorPHID(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2013-10-16 19:35:52 +02:00
|
|
|
|
|
|
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function getCapabilities() {
|
|
|
|
return array(
|
|
|
|
PhabricatorPolicyCapability::CAN_VIEW,
|
|
|
|
PhabricatorPolicyCapability::CAN_EDIT,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getPolicy($capability) {
|
|
|
|
if ($capability == PhabricatorPolicyCapability::CAN_VIEW) {
|
|
|
|
return $this->viewPolicy;
|
2014-12-31 17:23:47 +01:00
|
|
|
} else if ($capability == PhabricatorPolicyCapability::CAN_EDIT) {
|
|
|
|
return $this->editPolicy;
|
2013-10-16 19:35:52 +02:00
|
|
|
}
|
|
|
|
return PhabricatorPolicies::POLICY_NOONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
|
|
|
|
return ($user->getPHID() == $this->getAuthorPHID());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function describeAutomaticCapability($capability) {
|
|
|
|
return pht('The author of a paste can always view and edit it.');
|
|
|
|
}
|
|
|
|
|
2014-09-25 22:42:38 +02:00
|
|
|
|
|
|
|
/* -( PhabricatorDestructibleInterface )----------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function destroyObjectPermanently(
|
|
|
|
PhabricatorDestructionEngine $engine) {
|
|
|
|
|
|
|
|
if ($this->filePHID) {
|
|
|
|
$file = id(new PhabricatorFileQuery())
|
2015-05-15 23:07:17 +02:00
|
|
|
->setViewer($engine->getViewer())
|
2014-09-25 22:42:38 +02:00
|
|
|
->withPHIDs(array($this->filePHID))
|
|
|
|
->executeOne();
|
|
|
|
if ($file) {
|
|
|
|
$engine->destroyObject($file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->delete();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function getApplicationTransactionEditor() {
|
|
|
|
return new PhabricatorPasteEditor();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getApplicationTransactionObject() {
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getApplicationTransactionTemplate() {
|
|
|
|
return new PhabricatorPasteTransaction();
|
|
|
|
}
|
|
|
|
|
2014-12-04 22:58:52 +01:00
|
|
|
public function willRenderTimeline(
|
|
|
|
PhabricatorApplicationTransactionView $timeline,
|
|
|
|
AphrontRequest $request) {
|
|
|
|
|
|
|
|
return $timeline;
|
|
|
|
}
|
|
|
|
|
2015-06-05 02:45:24 +02:00
|
|
|
|
|
|
|
/* -( PhabricatorSpacesInterface )----------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function getSpacePHID() {
|
|
|
|
return $this->spacePHID;
|
|
|
|
}
|
|
|
|
|
2011-06-10 08:53:53 +02:00
|
|
|
}
|