@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Scramble file secrets when related objects change policies

Summary:
Ref T10262. Files have an internal secret key which is partially used to control access to them, and determines part of the URL you need to access them. Scramble (regenerate) the secret when:

- the view policy for the file itself changes (and the new policy is not "public" or "all users"); or
- the view policy or space for an object the file is attached to changes (and the file policy is not "public" or "all users").

This basically means that when you change the visibility of a task, any old URLs for attached files stop working and new ones are implicitly generated.

Test Plan:
- Attached a file to a task, used `SELECT * FROM file WHERE id = ...` to inspect the secret.
- Set view policy to public, same secret.
- Set view policy to me, new secret.
- Changed task view policy, new secret.
- Changed task space, new secret.
- Changed task title, same old secret.
- Added and ran unit tests which cover this behavior.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10262

Differential Revision: https://secure.phabricator.com/D15641

+195 -1
+4
src/applications/files/storage/PhabricatorFile.php
··· 139 139 return 'F'.$this->getID(); 140 140 } 141 141 142 + public function scrambleSecret() { 143 + return $this->setSecretKey($this->generateSecretKey()); 144 + } 145 + 142 146 public static function readUploadedFileData($spec) { 143 147 if (!$spec) { 144 148 throw new Exception(pht('No file was uploaded!'));
+127
src/applications/files/storage/__tests__/PhabricatorFileTestCase.php
··· 8 8 ); 9 9 } 10 10 11 + public function testFileDirectScramble() { 12 + // Changes to a file's view policy should scramble the file secret. 13 + 14 + $engine = new PhabricatorTestStorageEngine(); 15 + $data = Filesystem::readRandomCharacters(64); 16 + 17 + $author = $this->generateNewTestUser(); 18 + 19 + $params = array( 20 + 'name' => 'test.dat', 21 + 'viewPolicy' => PhabricatorPolicies::POLICY_USER, 22 + 'authorPHID' => $author->getPHID(), 23 + 'storageEngines' => array( 24 + $engine, 25 + ), 26 + ); 27 + 28 + $file = PhabricatorFile::newFromFileData($data, $params); 29 + 30 + $secret1 = $file->getSecretKey(); 31 + 32 + // First, change the name: this should not scramble the secret. 33 + $xactions = array(); 34 + $xactions[] = id(new PhabricatorFileTransaction()) 35 + ->setTransactionType(PhabricatorFileTransaction::TYPE_NAME) 36 + ->setNewValue('test.dat2'); 37 + 38 + $engine = id(new PhabricatorFileEditor()) 39 + ->setActor($author) 40 + ->setContentSource($this->newContentSource()) 41 + ->applyTransactions($file, $xactions); 42 + 43 + $file = $file->reload(); 44 + 45 + $secret2 = $file->getSecretKey(); 46 + 47 + $this->assertEqual( 48 + $secret1, 49 + $secret2, 50 + pht('No secret scramble on non-policy edit.')); 51 + 52 + // Now, change the view policy. This should scramble the secret. 53 + $xactions = array(); 54 + $xactions[] = id(new PhabricatorFileTransaction()) 55 + ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) 56 + ->setNewValue($author->getPHID()); 57 + 58 + $engine = id(new PhabricatorFileEditor()) 59 + ->setActor($author) 60 + ->setContentSource($this->newContentSource()) 61 + ->applyTransactions($file, $xactions); 62 + 63 + $file = $file->reload(); 64 + $secret3 = $file->getSecretKey(); 65 + 66 + $this->assertTrue( 67 + ($secret1 !== $secret3), 68 + pht('Changing file view policy should scramble secret.')); 69 + } 70 + 71 + public function testFileIndirectScramble() { 72 + // When a file is attached to an object like a task and the task view 73 + // policy changes, the file secret should be scrambled. This invalidates 74 + // old URIs if tasks get locked down. 75 + 76 + $engine = new PhabricatorTestStorageEngine(); 77 + $data = Filesystem::readRandomCharacters(64); 78 + 79 + $author = $this->generateNewTestUser(); 80 + 81 + $params = array( 82 + 'name' => 'test.dat', 83 + 'viewPolicy' => $author->getPHID(), 84 + 'authorPHID' => $author->getPHID(), 85 + 'storageEngines' => array( 86 + $engine, 87 + ), 88 + ); 89 + 90 + $file = PhabricatorFile::newFromFileData($data, $params); 91 + $secret1 = $file->getSecretKey(); 92 + 93 + $task = ManiphestTask::initializeNewTask($author); 94 + 95 + $xactions = array(); 96 + $xactions[] = id(new ManiphestTransaction()) 97 + ->setTransactionType(ManiphestTransaction::TYPE_TITLE) 98 + ->setNewValue(pht('File Scramble Test Task')); 99 + 100 + $xactions[] = id(new ManiphestTransaction()) 101 + ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION) 102 + ->setNewValue('{'.$file->getMonogram().'}'); 103 + 104 + id(new ManiphestTransactionEditor()) 105 + ->setActor($author) 106 + ->setContentSource($this->newContentSource()) 107 + ->applyTransactions($task, $xactions); 108 + 109 + $file = $file->reload(); 110 + $secret2 = $file->getSecretKey(); 111 + 112 + $this->assertEqual( 113 + $secret1, 114 + $secret2, 115 + pht( 116 + 'File policy should not scramble when attached to '. 117 + 'newly created object.')); 118 + 119 + $xactions = array(); 120 + $xactions[] = id(new ManiphestTransaction()) 121 + ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) 122 + ->setNewValue($author->getPHID()); 123 + 124 + id(new ManiphestTransactionEditor()) 125 + ->setActor($author) 126 + ->setContentSource($this->newContentSource()) 127 + ->applyTransactions($task, $xactions); 128 + 129 + $file = $file->reload(); 130 + $secret3 = $file->getSecretKey(); 131 + 132 + $this->assertTrue( 133 + ($secret1 !== $secret3), 134 + pht('Changing attached object view policy should scramble secret.')); 135 + } 136 + 137 + 11 138 public function testFileVisibility() { 12 139 $engine = new PhabricatorTestStorageEngine(); 13 140 $data = Filesystem::readRandomCharacters(64);
+64 -1
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 678 678 679 679 $editor->save(); 680 680 break; 681 + case PhabricatorTransactions::TYPE_VIEW_POLICY: 682 + case PhabricatorTransactions::TYPE_SPACE: 683 + $this->scrambleFileSecrets($object); 684 + break; 681 685 } 682 686 } 683 687 ··· 914 918 } 915 919 916 920 $xactions = $this->applyFinalEffects($object, $xactions); 917 - 918 921 if ($read_locking) { 919 922 $object->endReadLocking(); 920 923 $read_locking = false; ··· 3469 3472 } 3470 3473 3471 3474 return $phids; 3475 + } 3476 + 3477 + /** 3478 + * When the view policy for an object is changed, scramble the secret keys 3479 + * for attached files to invalidate existing URIs. 3480 + */ 3481 + private function scrambleFileSecrets($object) { 3482 + // If this is a newly created object, we don't need to scramble anything 3483 + // since it couldn't have been previously published. 3484 + if ($this->getIsNewObject()) { 3485 + return; 3486 + } 3487 + 3488 + // If the object is a file itself, scramble it. 3489 + if ($object instanceof PhabricatorFile) { 3490 + if ($this->shouldScramblePolicy($object->getViewPolicy())) { 3491 + $object->scrambleSecret(); 3492 + $object->save(); 3493 + } 3494 + } 3495 + 3496 + $phid = $object->getPHID(); 3497 + 3498 + $attached_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( 3499 + $phid, 3500 + PhabricatorObjectHasFileEdgeType::EDGECONST); 3501 + if (!$attached_phids) { 3502 + return; 3503 + } 3504 + 3505 + $omnipotent_viewer = PhabricatorUser::getOmnipotentUser(); 3506 + 3507 + $files = id(new PhabricatorFileQuery()) 3508 + ->setViewer($omnipotent_viewer) 3509 + ->withPHIDs($attached_phids) 3510 + ->execute(); 3511 + foreach ($files as $file) { 3512 + $view_policy = $file->getViewPolicy(); 3513 + if ($this->shouldScramblePolicy($view_policy)) { 3514 + $file->scrambleSecret(); 3515 + $file->save(); 3516 + } 3517 + } 3518 + } 3519 + 3520 + 3521 + /** 3522 + * Check if a policy is strong enough to justify scrambling. Objects which 3523 + * are set to very open policies don't need to scramble their files, and 3524 + * files with very open policies don't need to be scrambled when associated 3525 + * objects change. 3526 + */ 3527 + private function shouldScramblePolicy($policy) { 3528 + switch ($policy) { 3529 + case PhabricatorPolicies::POLICY_PUBLIC: 3530 + case PhabricatorPolicies::POLICY_USER: 3531 + return false; 3532 + } 3533 + 3534 + return true; 3472 3535 } 3473 3536 3474 3537 }