mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-30 09:20:58 +01:00
Make Herald rules obey policies during application
Summary: Ref T603. This closes the other major policy loophole in Herald, which was that you could write a rule like: When [Always], [Add me to CC] ...and end up getting email about everything. These rules are now enforced: - For a //personal// rule to trigger, you must be able to see the object, and you must be able to use the application the object exists in. - In contrast, //global// rules will //always// trigger. Also fixes some small bugs: - Policy control access to thumbnails was overly restrictive. - The Pholio and Maniphest Herald rules applied only the //last// "Add CC" or "Add Project" rules, since each rule overwrote previous rules. Test Plan: - Created "always cc me" herald and maniphest rules with a normal user. - Created task with "user" visibility, saw CC. - Created task with "no one" visibility, saw no CC and error message in transcript ("user can't see the object"). - Restricted Maniphest to administrators and created a task with "user" visibility. Same deal. - Created "user" and "no one" mocks and saw CC and no CC, respectively. - Thumbnail in Pholio worked properly. Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T603 Differential Revision: https://secure.phabricator.com/D7224
This commit is contained in:
parent
ae27ce0f7d
commit
e6d8e1a00a
10 changed files with 84 additions and 22 deletions
|
@ -7,21 +7,24 @@ final class PhabricatorFileTransformController
|
||||||
private $phid;
|
private $phid;
|
||||||
private $key;
|
private $key;
|
||||||
|
|
||||||
|
public function shouldRequireLogin() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public function willProcessRequest(array $data) {
|
public function willProcessRequest(array $data) {
|
||||||
$this->transform = $data['transform'];
|
$this->transform = $data['transform'];
|
||||||
$this->phid = $data['phid'];
|
$this->phid = $data['phid'];
|
||||||
$this->key = $data['key'];
|
$this->key = $data['key'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function shouldRequireLogin() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function processRequest() {
|
public function processRequest() {
|
||||||
$viewer = $this->getRequest()->getUser();
|
$viewer = $this->getRequest()->getUser();
|
||||||
|
|
||||||
|
// NOTE: This is a public/CDN endpoint, and permission to see files is
|
||||||
|
// controlled by knowing the secret key, not by authentication.
|
||||||
|
|
||||||
$file = id(new PhabricatorFileQuery())
|
$file = id(new PhabricatorFileQuery())
|
||||||
->setViewer($viewer)
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
->withPHIDs(array($this->phid))
|
->withPHIDs(array($this->phid))
|
||||||
->executeOne();
|
->executeOne();
|
||||||
if (!$file) {
|
if (!$file) {
|
||||||
|
@ -130,7 +133,7 @@ final class PhabricatorFileTransformController
|
||||||
PhabricatorTransformedFile $xform) {
|
PhabricatorTransformedFile $xform) {
|
||||||
|
|
||||||
$file = id(new PhabricatorFileQuery())
|
$file = id(new PhabricatorFileQuery())
|
||||||
->setViewer($this->getRequest()->getUser())
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
->withPHIDs(array($xform->getTransformedPHID()))
|
->withPHIDs(array($xform->getTransformedPHID()))
|
||||||
->executeOne();
|
->executeOne();
|
||||||
if (!$file) {
|
if (!$file) {
|
||||||
|
|
|
@ -114,6 +114,7 @@ abstract class HeraldAdapter {
|
||||||
|
|
||||||
abstract public function getAdapterContentName();
|
abstract public function getAdapterContentName();
|
||||||
abstract public function getAdapterApplicationClass();
|
abstract public function getAdapterApplicationClass();
|
||||||
|
abstract public function getObject();
|
||||||
|
|
||||||
|
|
||||||
/* -( Fields )------------------------------------------------------------- */
|
/* -( Fields )------------------------------------------------------------- */
|
||||||
|
|
|
@ -31,6 +31,10 @@ final class HeraldCommitAdapter extends HeraldAdapter {
|
||||||
return 'PhabricatorApplicationDiffusion';
|
return 'PhabricatorApplicationDiffusion';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getObject() {
|
||||||
|
return $this->commit;
|
||||||
|
}
|
||||||
|
|
||||||
public function getAdapterContentType() {
|
public function getAdapterContentType() {
|
||||||
return 'commit';
|
return 'commit';
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,10 @@ final class HeraldDifferentialRevisionAdapter extends HeraldAdapter {
|
||||||
return 'PhabricatorApplicationDifferential';
|
return 'PhabricatorApplicationDifferential';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getObject() {
|
||||||
|
return $this->revision;
|
||||||
|
}
|
||||||
|
|
||||||
public function getAdapterContentType() {
|
public function getAdapterContentType() {
|
||||||
return 'differential';
|
return 'differential';
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,10 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter {
|
||||||
return $this->task;
|
return $this->task;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getObject() {
|
||||||
|
return $this->task;
|
||||||
|
}
|
||||||
|
|
||||||
private function setCcPHIDs(array $cc_phids) {
|
private function setCcPHIDs(array $cc_phids) {
|
||||||
$this->ccPHIDs = $cc_phids;
|
$this->ccPHIDs = $cc_phids;
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -118,11 +122,9 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter {
|
||||||
pht('Great success at doing nothing.'));
|
pht('Great success at doing nothing.'));
|
||||||
break;
|
break;
|
||||||
case self::ACTION_ADD_CC:
|
case self::ACTION_ADD_CC:
|
||||||
$add_cc = array();
|
|
||||||
foreach ($effect->getTarget() as $phid) {
|
foreach ($effect->getTarget() as $phid) {
|
||||||
$add_cc[$phid] = true;
|
$this->ccPHIDs[] = $phid;
|
||||||
}
|
}
|
||||||
$this->setCcPHIDs(array_keys($add_cc));
|
|
||||||
$result[] = new HeraldApplyTranscript(
|
$result[] = new HeraldApplyTranscript(
|
||||||
$effect,
|
$effect,
|
||||||
true,
|
true,
|
||||||
|
@ -143,11 +145,9 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter {
|
||||||
pht('Assigned task.'));
|
pht('Assigned task.'));
|
||||||
break;
|
break;
|
||||||
case self::ACTION_ADD_PROJECTS:
|
case self::ACTION_ADD_PROJECTS:
|
||||||
$add_projects = array();
|
|
||||||
foreach ($effect->getTarget() as $phid) {
|
foreach ($effect->getTarget() as $phid) {
|
||||||
$add_projects[$phid] = true;
|
$this->projectPHIDs[] = $phid;
|
||||||
}
|
}
|
||||||
$this->setProjectPHIDs(array_keys($add_projects));
|
|
||||||
$result[] = new HeraldApplyTranscript(
|
$result[] = new HeraldApplyTranscript(
|
||||||
$effect,
|
$effect,
|
||||||
true,
|
true,
|
||||||
|
|
|
@ -12,6 +12,10 @@ final class HeraldPholioMockAdapter extends HeraldAdapter {
|
||||||
return 'PhabricatorApplicationPholio';
|
return 'PhabricatorApplicationPholio';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getObject() {
|
||||||
|
return $this->mock;
|
||||||
|
}
|
||||||
|
|
||||||
public function setMock(PholioMock $mock) {
|
public function setMock(PholioMock $mock) {
|
||||||
$this->mock = $mock;
|
$this->mock = $mock;
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -97,11 +101,9 @@ final class HeraldPholioMockAdapter extends HeraldAdapter {
|
||||||
pht('Great success at doing nothing.'));
|
pht('Great success at doing nothing.'));
|
||||||
break;
|
break;
|
||||||
case self::ACTION_ADD_CC:
|
case self::ACTION_ADD_CC:
|
||||||
$add_cc = array();
|
|
||||||
foreach ($effect->getTarget() as $phid) {
|
foreach ($effect->getTarget() as $phid) {
|
||||||
$add_cc[$phid] = true;
|
$this->ccPHIDs[] = $phid;
|
||||||
}
|
}
|
||||||
$this->setCcPHIDs(array_keys($add_cc));
|
|
||||||
$result[] = new HeraldApplyTranscript(
|
$result[] = new HeraldApplyTranscript(
|
||||||
$effect,
|
$effect,
|
||||||
true,
|
true,
|
||||||
|
|
|
@ -49,7 +49,8 @@ final class HeraldNewController extends HeraldController {
|
||||||
HeraldRuleTypeConfig::RULE_TYPE_PERSONAL =>
|
HeraldRuleTypeConfig::RULE_TYPE_PERSONAL =>
|
||||||
pht(
|
pht(
|
||||||
'Personal rules notify you about events. You own them, but they can '.
|
'Personal rules notify you about events. You own them, but they can '.
|
||||||
'only affect you.'),
|
'only affect you. Personal rules only trigger for objects you have '.
|
||||||
|
'permission to see.'),
|
||||||
HeraldRuleTypeConfig::RULE_TYPE_GLOBAL =>
|
HeraldRuleTypeConfig::RULE_TYPE_GLOBAL =>
|
||||||
phutil_implode_html(
|
phutil_implode_html(
|
||||||
phutil_tag('br'),
|
phutil_tag('br'),
|
||||||
|
@ -57,7 +58,7 @@ final class HeraldNewController extends HeraldController {
|
||||||
array(
|
array(
|
||||||
pht(
|
pht(
|
||||||
'Global rules notify anyone about events. Global rules can '.
|
'Global rules notify anyone about events. Global rules can '.
|
||||||
'bypass access control policies.'),
|
'bypass access control policies and act on any object.'),
|
||||||
$global_link,
|
$global_link,
|
||||||
))),
|
))),
|
||||||
);
|
);
|
||||||
|
|
|
@ -233,15 +233,23 @@ final class HeraldEngine {
|
||||||
|
|
||||||
$local_version = id(new HeraldRule())->getConfigVersion();
|
$local_version = id(new HeraldRule())->getConfigVersion();
|
||||||
if ($rule->getConfigVersion() > $local_version) {
|
if ($rule->getConfigVersion() > $local_version) {
|
||||||
$reason = "Rule could not be processed, it was created with a newer ".
|
$reason = pht(
|
||||||
"version of Herald.";
|
"Rule could not be processed, it was created with a newer version ".
|
||||||
|
"of Herald.");
|
||||||
$result = false;
|
$result = false;
|
||||||
} else if (!$conditions) {
|
} else if (!$conditions) {
|
||||||
$reason = "Rule failed automatically because it has no conditions.";
|
$reason = pht(
|
||||||
|
"Rule failed automatically because it has no conditions.");
|
||||||
$result = false;
|
$result = false;
|
||||||
} else if (!$rule->hasValidAuthor()) {
|
} else if (!$rule->hasValidAuthor()) {
|
||||||
$reason = "Rule failed automatically because its owner is invalid ".
|
$reason = pht(
|
||||||
"or disabled.";
|
"Rule failed automatically because its owner is invalid ".
|
||||||
|
"or disabled.");
|
||||||
|
$result = false;
|
||||||
|
} else if (!$this->canAuthorViewObject($rule, $object)) {
|
||||||
|
$reason = pht(
|
||||||
|
"Rule failed automatically because it is a personal rule and its ".
|
||||||
|
"owner can not see the object.");
|
||||||
$result = false;
|
$result = false;
|
||||||
} else {
|
} else {
|
||||||
foreach ($conditions as $condition) {
|
foreach ($conditions as $condition) {
|
||||||
|
@ -361,4 +369,32 @@ final class HeraldEngine {
|
||||||
return $effects;
|
return $effects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function canAuthorViewObject(
|
||||||
|
HeraldRule $rule,
|
||||||
|
HeraldAdapter $adapter) {
|
||||||
|
|
||||||
|
// Authorship is irrelevant for global rules.
|
||||||
|
if ($rule->isGlobalRule()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The author must be able to create rules for the adapter's content type.
|
||||||
|
// In particular, this means that the application must be installed and
|
||||||
|
// accessible to the user. For example, if a user writes a Differential
|
||||||
|
// rule and then loses access to Differential, this disables the rule.
|
||||||
|
$enabled = HeraldAdapter::getEnabledAdapterMap($rule->getAuthor());
|
||||||
|
if (empty($enabled[$adapter->getAdapterContentType()])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, the author must be able to see the object itself. You can't
|
||||||
|
// write a personal rule that CC's you on revisions you wouldn't otherwise
|
||||||
|
// be able to see, for example.
|
||||||
|
$object = $adapter->getObject();
|
||||||
|
return PhabricatorPolicyFilter::hasCapability(
|
||||||
|
$rule->getAuthor(),
|
||||||
|
$object,
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -211,6 +211,7 @@ final class HeraldRuleQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
$rule->attachValidAuthor(true);
|
$rule->attachValidAuthor(true);
|
||||||
|
$rule->attachAuthor($users[$author_phid]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ final class HeraldRule extends HeraldDAO
|
||||||
|
|
||||||
private $ruleApplied = self::ATTACHABLE; // phids for which this rule has been applied
|
private $ruleApplied = self::ATTACHABLE; // phids for which this rule has been applied
|
||||||
private $validAuthor = self::ATTACHABLE;
|
private $validAuthor = self::ATTACHABLE;
|
||||||
|
private $author = self::ATTACHABLE;
|
||||||
private $conditions;
|
private $conditions;
|
||||||
private $actions;
|
private $actions;
|
||||||
|
|
||||||
|
@ -167,6 +168,15 @@ final class HeraldRule extends HeraldDAO
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getAuthor() {
|
||||||
|
return $this->assertAttached($this->author);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attachAuthor(PhabricatorUser $user) {
|
||||||
|
$this->author = $user;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function isGlobalRule() {
|
public function isGlobalRule() {
|
||||||
return ($this->getRuleType() === HeraldRuleTypeConfig::RULE_TYPE_GLOBAL);
|
return ($this->getRuleType() === HeraldRuleTypeConfig::RULE_TYPE_GLOBAL);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue