@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.

Add test coverage to the PasswordEngine upgrade workflow and fix a few bugs

Summary:
Ref T13043. When we verify a password and a better hasher is available, we automatically upgrade the stored hash to the stronger hasher.

Add test coverage for this workflow and fix a few bugs and issues, mostly related to shuffling the old hasher name into the transaction.

This doesn't touch anything user-visible yet.

Test Plan: Ran unit tests.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13043

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

+117 -3
+2
src/__phutil_library_map__.php
··· 2099 2099 'PhabricatorAuthPasswordRevoker' => 'applications/auth/revoker/PhabricatorAuthPasswordRevoker.php', 2100 2100 'PhabricatorAuthPasswordTestCase' => 'applications/auth/__tests__/PhabricatorAuthPasswordTestCase.php', 2101 2101 'PhabricatorAuthPasswordTransaction' => 'applications/auth/storage/PhabricatorAuthPasswordTransaction.php', 2102 + 'PhabricatorAuthPasswordTransactionQuery' => 'applications/auth/query/PhabricatorAuthPasswordTransactionQuery.php', 2102 2103 'PhabricatorAuthPasswordTransactionType' => 'applications/auth/xaction/PhabricatorAuthPasswordTransactionType.php', 2103 2104 'PhabricatorAuthPasswordUpgradeTransaction' => 'applications/auth/xaction/PhabricatorAuthPasswordUpgradeTransaction.php', 2104 2105 'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php', ··· 7393 7394 'PhabricatorAuthPasswordRevoker' => 'PhabricatorAuthRevoker', 7394 7395 'PhabricatorAuthPasswordTestCase' => 'PhabricatorTestCase', 7395 7396 'PhabricatorAuthPasswordTransaction' => 'PhabricatorModularTransaction', 7397 + 'PhabricatorAuthPasswordTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 7396 7398 'PhabricatorAuthPasswordTransactionType' => 'PhabricatorModularTransactionType', 7397 7399 'PhabricatorAuthPasswordUpgradeTransaction' => 'PhabricatorAuthPasswordTransactionType', 7398 7400 'PhabricatorAuthProvider' => 'Phobject',
+85
src/applications/auth/__tests__/PhabricatorAuthPasswordTestCase.php
··· 99 99 $this->assertTrue($account_engine->isUniquePassword($password2)); 100 100 } 101 101 102 + public function testPasswordUpgrade() { 103 + $weak_hasher = new PhabricatorIteratedMD5PasswordHasher(); 104 + 105 + // Make sure we have two different hashers, and that the second one is 106 + // stronger than iterated MD5. The most common reason this would fail is 107 + // if an install does not have bcrypt available. 108 + $strong_hasher = PhabricatorPasswordHasher::getBestHasher(); 109 + if ($strong_hasher->getStrength() <= $weak_hasher->getStrength()) { 110 + $this->assertSkipped( 111 + pht( 112 + 'Multiple password hashers of different strengths are not '. 113 + 'available, so hash upgrading can not be tested.')); 114 + } 115 + 116 + $envelope = new PhutilOpaqueEnvelope('lunar1997'); 117 + 118 + $user = $this->generateNewTestUser(); 119 + $type = PhabricatorAuthPassword::PASSWORD_TYPE_TEST; 120 + $content_source = $this->newContentSource(); 121 + 122 + $engine = id(new PhabricatorAuthPasswordEngine()) 123 + ->setViewer($user) 124 + ->setContentSource($content_source) 125 + ->setPasswordType($type) 126 + ->setObject($user); 127 + 128 + $password = PhabricatorAuthPassword::initializeNewPassword($user, $type) 129 + ->setPasswordWithHasher($envelope, $user, $weak_hasher) 130 + ->save(); 131 + 132 + $weak_name = $weak_hasher->getHashName(); 133 + $strong_name = $strong_hasher->getHashName(); 134 + 135 + // Since we explicitly used the weak hasher, the password should have 136 + // been hashed with it. 137 + $actual_hasher = $password->getHasher(); 138 + $this->assertEqual($weak_name, $actual_hasher->getHashName()); 139 + 140 + $is_valid = $engine 141 + ->setUpgradeHashers(false) 142 + ->isValidPassword($envelope, $user); 143 + $password->reload(); 144 + 145 + // Since we disabled hasher upgrading, the password should not have been 146 + // rehashed. 147 + $this->assertTrue($is_valid); 148 + $actual_hasher = $password->getHasher(); 149 + $this->assertEqual($weak_name, $actual_hasher->getHashName()); 150 + 151 + $is_valid = $engine 152 + ->setUpgradeHashers(true) 153 + ->isValidPassword($envelope, $user); 154 + $password->reload(); 155 + 156 + // Now that we enabled hasher upgrading, the password should have been 157 + // automatically rehashed into the stronger format. 158 + $this->assertTrue($is_valid); 159 + $actual_hasher = $password->getHasher(); 160 + $this->assertEqual($strong_name, $actual_hasher->getHashName()); 161 + 162 + // We should also have an "upgrade" transaction in the transaction record 163 + // now which records the two hasher names. 164 + $xactions = id(new PhabricatorAuthPasswordTransactionQuery()) 165 + ->setViewer($user) 166 + ->withObjectPHIDs(array($password->getPHID())) 167 + ->withTransactionTypes( 168 + array( 169 + PhabricatorAuthPasswordUpgradeTransaction::TRANSACTIONTYPE, 170 + )) 171 + ->execute(); 172 + 173 + $this->assertEqual(1, count($xactions)); 174 + $xaction = head($xactions); 175 + 176 + $this->assertEqual($weak_name, $xaction->getOldValue()); 177 + $this->assertEqual($strong_name, $xaction->getNewValue()); 178 + 179 + $is_valid = $engine 180 + ->isValidPassword($envelope, $user); 181 + 182 + // Finally, the password should still be valid after all the dust has 183 + // settled. 184 + $this->assertTrue($is_valid); 185 + } 186 + 102 187 private function revokePassword( 103 188 PhabricatorUser $actor, 104 189 PhabricatorAuthPassword $password) {
+11
src/applications/auth/editor/PhabricatorAuthPasswordEditor.php
··· 3 3 final class PhabricatorAuthPasswordEditor 4 4 extends PhabricatorApplicationTransactionEditor { 5 5 6 + private $oldHasher; 7 + 8 + public function setOldHasher(PhabricatorPasswordHasher $old_hasher) { 9 + $this->oldHasher = $old_hasher; 10 + return $this; 11 + } 12 + 13 + public function getOldHasher() { 14 + return $this->oldHasher; 15 + } 16 + 6 17 public function getEditorApplicationClass() { 7 18 return 'PhabricatorAuthApplication'; 8 19 }
+1 -1
src/applications/auth/engine/PhabricatorAuthPasswordEngine.php
··· 224 224 225 225 $xactions[] = $password->getApplicationTransactionTemplate() 226 226 ->setTransactionType($upgrade_type) 227 - ->setOldValue($old_hasher->getHashName()) 228 227 ->setNewValue($new_hasher->getHashName()); 229 228 230 229 $editor = $password->getApplicationTransactionEditor() ··· 232 231 ->setContinueOnNoEffect(true) 233 232 ->setContinueOnMissingFields(true) 234 233 ->setContentSource($content_source) 234 + ->setOldHasher($old_hasher) 235 235 ->applyTransactions($password, $xactions); 236 236 } 237 237 unset($unguarded);
+10
src/applications/auth/query/PhabricatorAuthPasswordTransactionQuery.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuthPasswordTransactionQuery 4 + extends PhabricatorApplicationTransactionQuery { 5 + 6 + public function getTemplateApplicationTransaction() { 7 + return new PhabricatorAuthPasswordTransaction(); 8 + } 9 + 10 + }
+8 -2
src/applications/auth/xaction/PhabricatorAuthPasswordUpgradeTransaction.php
··· 6 6 const TRANSACTIONTYPE = 'password.upgrade'; 7 7 8 8 public function generateOldValue($object) { 9 - return $this->getStorage()->getOldValue(); 9 + $old_hasher = $this->getEditor()->getOldHasher(); 10 + 11 + if (!$old_hasher) { 12 + throw new PhutilInvalidStateException('setOldHasher'); 13 + } 14 + 15 + return $old_hasher->getHashName(); 10 16 } 11 17 12 18 public function generateNewValue($object, $value) { 13 - return (bool)$value; 19 + return $value; 14 20 } 15 21 16 22 public function getTitle() {