mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-18 11:30:55 +01:00
Add a more modern object for storing password hashes
Summary: Ref T13043. Currently: - Passwords are stored separately in the "VCS Passwords" and "User" tables and don't share as much code as they could. - Because User objects are all over the place in the code, password hashes are all over the place too (i.e., often somewhere in process memory). This is a very low-severity, theoretical sort of issue, but it could make leaving a stray `var_dump()` in the code somewhere a lot more dangerous than it otherwise is. Even if we never do this, third-party developers might. So it "feels nice" to imagine separating this data into a different table that we rarely load. - Passwords can not be //revoked//. They can be //deleted//, but users can set the same password again. If you believe or suspect that a password may have been compromised, you might reasonably prefer to revoke it and force the user to select a //different// password. This change prepares to remedy these issues by adding a new, more modern dedicated password storage table which supports storing multiple password types (account vs VCS), gives passwords real PHIDs and transactions, supports DestructionEngine, supports revocation, and supports `bin/auth revoke`. It doesn't actually make anything use this new table yet. Future changes will migrate VCS passwords and account passwords to this table. (This also gives third party applications a reasonable place to store password hashes in a consistent way if they have some need for it.) Test Plan: Added some basic unit tests to cover general behavior. This is just skeleton code for now and will get more thorough testing when applications move. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13043 Differential Revision: https://secure.phabricator.com/D18894
This commit is contained in:
parent
fa1ecb7f66
commit
9c00a43784
14 changed files with 563 additions and 1 deletions
10
resources/sql/autopatches/20180120.auth.01.password.sql
Normal file
10
resources/sql/autopatches/20180120.auth.01.password.sql
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
CREATE TABLE {$NAMESPACE}_auth.auth_password (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
phid VARBINARY(64) NOT NULL,
|
||||||
|
objectPHID VARBINARY(64) NOT NULL,
|
||||||
|
passwordType VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||||
|
passwordHash VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||||
|
isRevoked BOOL NOT NULL,
|
||||||
|
dateCreated INT UNSIGNED NOT NULL,
|
||||||
|
dateModified INT UNSIGNED NOT NULL
|
||||||
|
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -0,0 +1,19 @@
|
||||||
|
CREATE TABLE {$NAMESPACE}_auth.auth_passwordtransaction (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
phid VARBINARY(64) NOT NULL,
|
||||||
|
authorPHID VARBINARY(64) NOT NULL,
|
||||||
|
objectPHID VARBINARY(64) NOT NULL,
|
||||||
|
viewPolicy VARBINARY(64) NOT NULL,
|
||||||
|
editPolicy VARBINARY(64) NOT NULL,
|
||||||
|
commentPHID VARBINARY(64) DEFAULT NULL,
|
||||||
|
commentVersion INT UNSIGNED NOT NULL,
|
||||||
|
transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||||
|
oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||||
|
newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||||
|
contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||||
|
metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||||
|
dateCreated INT UNSIGNED NOT NULL,
|
||||||
|
dateModified INT UNSIGNED NOT NULL,
|
||||||
|
UNIQUE KEY `key_phid` (`phid`),
|
||||||
|
KEY `key_object` (`objectPHID`)
|
||||||
|
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -2089,7 +2089,16 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorAuthOldOAuthRedirectController' => 'applications/auth/controller/PhabricatorAuthOldOAuthRedirectController.php',
|
'PhabricatorAuthOldOAuthRedirectController' => 'applications/auth/controller/PhabricatorAuthOldOAuthRedirectController.php',
|
||||||
'PhabricatorAuthOneTimeLoginController' => 'applications/auth/controller/PhabricatorAuthOneTimeLoginController.php',
|
'PhabricatorAuthOneTimeLoginController' => 'applications/auth/controller/PhabricatorAuthOneTimeLoginController.php',
|
||||||
'PhabricatorAuthOneTimeLoginTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php',
|
'PhabricatorAuthOneTimeLoginTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php',
|
||||||
|
'PhabricatorAuthPassword' => 'applications/auth/storage/PhabricatorAuthPassword.php',
|
||||||
|
'PhabricatorAuthPasswordEditor' => 'applications/auth/editor/PhabricatorAuthPasswordEditor.php',
|
||||||
|
'PhabricatorAuthPasswordPHIDType' => 'applications/auth/phid/PhabricatorAuthPasswordPHIDType.php',
|
||||||
|
'PhabricatorAuthPasswordQuery' => 'applications/auth/query/PhabricatorAuthPasswordQuery.php',
|
||||||
'PhabricatorAuthPasswordResetTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthPasswordResetTemporaryTokenType.php',
|
'PhabricatorAuthPasswordResetTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthPasswordResetTemporaryTokenType.php',
|
||||||
|
'PhabricatorAuthPasswordRevokeTransaction' => 'applications/auth/xaction/PhabricatorAuthPasswordRevokeTransaction.php',
|
||||||
|
'PhabricatorAuthPasswordRevoker' => 'applications/auth/revoker/PhabricatorAuthPasswordRevoker.php',
|
||||||
|
'PhabricatorAuthPasswordTestCase' => 'applications/auth/__tests__/PhabricatorAuthPasswordTestCase.php',
|
||||||
|
'PhabricatorAuthPasswordTransaction' => 'applications/auth/storage/PhabricatorAuthPasswordTransaction.php',
|
||||||
|
'PhabricatorAuthPasswordTransactionType' => 'applications/auth/xaction/PhabricatorAuthPasswordTransactionType.php',
|
||||||
'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php',
|
'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php',
|
||||||
'PhabricatorAuthProviderConfig' => 'applications/auth/storage/PhabricatorAuthProviderConfig.php',
|
'PhabricatorAuthProviderConfig' => 'applications/auth/storage/PhabricatorAuthProviderConfig.php',
|
||||||
'PhabricatorAuthProviderConfigController' => 'applications/auth/controller/config/PhabricatorAuthProviderConfigController.php',
|
'PhabricatorAuthProviderConfigController' => 'applications/auth/controller/config/PhabricatorAuthProviderConfigController.php',
|
||||||
|
@ -3481,6 +3490,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorPagerUIExample' => 'applications/uiexample/examples/PhabricatorPagerUIExample.php',
|
'PhabricatorPagerUIExample' => 'applications/uiexample/examples/PhabricatorPagerUIExample.php',
|
||||||
'PhabricatorPassphraseApplication' => 'applications/passphrase/application/PhabricatorPassphraseApplication.php',
|
'PhabricatorPassphraseApplication' => 'applications/passphrase/application/PhabricatorPassphraseApplication.php',
|
||||||
'PhabricatorPasswordAuthProvider' => 'applications/auth/provider/PhabricatorPasswordAuthProvider.php',
|
'PhabricatorPasswordAuthProvider' => 'applications/auth/provider/PhabricatorPasswordAuthProvider.php',
|
||||||
|
'PhabricatorPasswordDestructionEngineExtension' => 'applications/auth/extension/PhabricatorPasswordDestructionEngineExtension.php',
|
||||||
'PhabricatorPasswordHasher' => 'infrastructure/util/password/PhabricatorPasswordHasher.php',
|
'PhabricatorPasswordHasher' => 'infrastructure/util/password/PhabricatorPasswordHasher.php',
|
||||||
'PhabricatorPasswordHasherTestCase' => 'infrastructure/util/password/__tests__/PhabricatorPasswordHasherTestCase.php',
|
'PhabricatorPasswordHasherTestCase' => 'infrastructure/util/password/__tests__/PhabricatorPasswordHasherTestCase.php',
|
||||||
'PhabricatorPasswordHasherUnavailableException' => 'infrastructure/util/password/PhabricatorPasswordHasherUnavailableException.php',
|
'PhabricatorPasswordHasherUnavailableException' => 'infrastructure/util/password/PhabricatorPasswordHasherUnavailableException.php',
|
||||||
|
@ -7366,7 +7376,21 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorAuthOldOAuthRedirectController' => 'PhabricatorAuthController',
|
'PhabricatorAuthOldOAuthRedirectController' => 'PhabricatorAuthController',
|
||||||
'PhabricatorAuthOneTimeLoginController' => 'PhabricatorAuthController',
|
'PhabricatorAuthOneTimeLoginController' => 'PhabricatorAuthController',
|
||||||
'PhabricatorAuthOneTimeLoginTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType',
|
'PhabricatorAuthOneTimeLoginTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType',
|
||||||
|
'PhabricatorAuthPassword' => array(
|
||||||
|
'PhabricatorAuthDAO',
|
||||||
|
'PhabricatorPolicyInterface',
|
||||||
|
'PhabricatorDestructibleInterface',
|
||||||
|
'PhabricatorApplicationTransactionInterface',
|
||||||
|
),
|
||||||
|
'PhabricatorAuthPasswordEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
|
'PhabricatorAuthPasswordPHIDType' => 'PhabricatorPHIDType',
|
||||||
|
'PhabricatorAuthPasswordQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'PhabricatorAuthPasswordResetTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType',
|
'PhabricatorAuthPasswordResetTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType',
|
||||||
|
'PhabricatorAuthPasswordRevokeTransaction' => 'PhabricatorAuthPasswordTransactionType',
|
||||||
|
'PhabricatorAuthPasswordRevoker' => 'PhabricatorAuthRevoker',
|
||||||
|
'PhabricatorAuthPasswordTestCase' => 'PhabricatorTestCase',
|
||||||
|
'PhabricatorAuthPasswordTransaction' => 'PhabricatorApplicationTransaction',
|
||||||
|
'PhabricatorAuthPasswordTransactionType' => 'PhabricatorModularTransactionType',
|
||||||
'PhabricatorAuthProvider' => 'Phobject',
|
'PhabricatorAuthProvider' => 'Phobject',
|
||||||
'PhabricatorAuthProviderConfig' => array(
|
'PhabricatorAuthProviderConfig' => array(
|
||||||
'PhabricatorAuthDAO',
|
'PhabricatorAuthDAO',
|
||||||
|
@ -8984,6 +9008,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorPagerUIExample' => 'PhabricatorUIExample',
|
'PhabricatorPagerUIExample' => 'PhabricatorUIExample',
|
||||||
'PhabricatorPassphraseApplication' => 'PhabricatorApplication',
|
'PhabricatorPassphraseApplication' => 'PhabricatorApplication',
|
||||||
'PhabricatorPasswordAuthProvider' => 'PhabricatorAuthProvider',
|
'PhabricatorPasswordAuthProvider' => 'PhabricatorAuthProvider',
|
||||||
|
'PhabricatorPasswordDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
|
||||||
'PhabricatorPasswordHasher' => 'Phobject',
|
'PhabricatorPasswordHasher' => 'Phobject',
|
||||||
'PhabricatorPasswordHasherTestCase' => 'PhabricatorTestCase',
|
'PhabricatorPasswordHasherTestCase' => 'PhabricatorTestCase',
|
||||||
'PhabricatorPasswordHasherUnavailableException' => 'Exception',
|
'PhabricatorPasswordHasherUnavailableException' => 'Exception',
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorAuthPasswordTestCase extends PhabricatorTestCase {
|
||||||
|
|
||||||
|
protected function getPhabricatorTestCaseConfiguration() {
|
||||||
|
return array(
|
||||||
|
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCompare() {
|
||||||
|
$password1 = new PhutilOpaqueEnvelope('hunter2');
|
||||||
|
$password2 = new PhutilOpaqueEnvelope('hunter3');
|
||||||
|
|
||||||
|
$user = $this->generateNewTestUser();
|
||||||
|
$type = PhabricatorAuthPassword::PASSWORD_TYPE_TEST;
|
||||||
|
|
||||||
|
$pass = PhabricatorAuthPassword::initializeNewPassword($user, $type)
|
||||||
|
->setPassword($password1, $user)
|
||||||
|
->save();
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
$pass->comparePassword($password1, $user),
|
||||||
|
pht('Good password should match.'));
|
||||||
|
|
||||||
|
$this->assertFalse(
|
||||||
|
$pass->comparePassword($password2, $user),
|
||||||
|
pht('Bad password should not match.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorAuthPasswordEditor
|
||||||
|
extends PhabricatorApplicationTransactionEditor {
|
||||||
|
|
||||||
|
public function getEditorApplicationClass() {
|
||||||
|
return 'PhabricatorAuthApplication';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEditorObjectsDescription() {
|
||||||
|
return pht('Passwords');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreateObjectTitle($author, $object) {
|
||||||
|
return pht('%s created this password.', $author);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreateObjectTitleForFeed($author, $object) {
|
||||||
|
return pht('%s created %s.', $author, $object);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorPasswordDestructionEngineExtension
|
||||||
|
extends PhabricatorDestructionEngineExtension {
|
||||||
|
|
||||||
|
const EXTENSIONKEY = 'passwords';
|
||||||
|
|
||||||
|
public function getExtensionName() {
|
||||||
|
return pht('Passwords');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroyObject(
|
||||||
|
PhabricatorDestructionEngine $engine,
|
||||||
|
$object) {
|
||||||
|
|
||||||
|
$viewer = $engine->getViewer();
|
||||||
|
$object_phid = $object->getPHID();
|
||||||
|
|
||||||
|
$passwords = id(new PhabricatorAuthPasswordQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withObjectPHIDs(array($object_phid))
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
foreach ($passwords as $password) {
|
||||||
|
$engine->destroyObject($password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorAuthPasswordPHIDType extends PhabricatorPHIDType {
|
||||||
|
|
||||||
|
const TYPECONST = 'APAS';
|
||||||
|
|
||||||
|
public function getTypeName() {
|
||||||
|
return pht('Auth Password');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newObject() {
|
||||||
|
return new PhabricatorAuthPassword();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPHIDTypeApplicationClass() {
|
||||||
|
return 'PhabricatorAuthApplication';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildQueryForObjects(
|
||||||
|
PhabricatorObjectQuery $query,
|
||||||
|
array $phids) {
|
||||||
|
return id(new PhabricatorAuthPasswordQuery())
|
||||||
|
->withPHIDs($phids);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadHandles(
|
||||||
|
PhabricatorHandleQuery $query,
|
||||||
|
array $handles,
|
||||||
|
array $objects) {
|
||||||
|
|
||||||
|
foreach ($handles as $phid => $handle) {
|
||||||
|
$password = $objects[$phid];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
114
src/applications/auth/query/PhabricatorAuthPasswordQuery.php
Normal file
114
src/applications/auth/query/PhabricatorAuthPasswordQuery.php
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorAuthPasswordQuery
|
||||||
|
extends PhabricatorCursorPagedPolicyAwareQuery {
|
||||||
|
|
||||||
|
private $ids;
|
||||||
|
private $phids;
|
||||||
|
private $objectPHIDs;
|
||||||
|
private $passwordTypes;
|
||||||
|
private $isRevoked;
|
||||||
|
|
||||||
|
public function withIDs(array $ids) {
|
||||||
|
$this->ids = $ids;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withPHIDs(array $phids) {
|
||||||
|
$this->phids = $phids;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withObjectPHIDs(array $object_phids) {
|
||||||
|
$this->objectPHIDs = $object_phids;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withPasswordTypes(array $types) {
|
||||||
|
$this->passwordTypes = $types;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withIsRevoked($is_revoked) {
|
||||||
|
$this->isRevoked = $is_revoked;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newResultObject() {
|
||||||
|
return new PhabricatorAuthPassword();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function loadPage() {
|
||||||
|
return $this->loadStandardPage($this->newResultObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||||
|
$where = parent::buildWhereClauseParts($conn);
|
||||||
|
|
||||||
|
if ($this->ids !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'id IN (%Ld)',
|
||||||
|
$this->ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->phids !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'phid IN (%Ls)',
|
||||||
|
$this->phids);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->objectPHIDs !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'objectPHID IN (%Ls)',
|
||||||
|
$this->objectPHIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->passwordTypes !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'passwordType IN (%Ls)',
|
||||||
|
$this->passwordTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isRevoked !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'isRevoked = %d',
|
||||||
|
(int)$this->isRevoked);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $where;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function willFilterPage(array $passwords) {
|
||||||
|
$object_phids = mpull($passwords, 'getObjectPHID');
|
||||||
|
|
||||||
|
$objects = id(new PhabricatorObjectQuery())
|
||||||
|
->setViewer($this->getViewer())
|
||||||
|
->setParentQuery($this)
|
||||||
|
->withPHIDs($object_phids)
|
||||||
|
->execute();
|
||||||
|
$objects = mpull($objects, null, 'getPHID');
|
||||||
|
|
||||||
|
foreach ($passwords as $key => $password) {
|
||||||
|
$object = idx($objects, $password->getObjectPHID());
|
||||||
|
if (!$object) {
|
||||||
|
unset($passwords[$key]);
|
||||||
|
$this->didRejectResult($password);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$password->attachObject($object);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $passwords;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQueryApplicationClass() {
|
||||||
|
return 'PhabricatorAuthApplication';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorAuthPasswordRevoker
|
||||||
|
extends PhabricatorAuthRevoker {
|
||||||
|
|
||||||
|
const REVOKERKEY = 'password';
|
||||||
|
|
||||||
|
public function revokeAllCredentials() {
|
||||||
|
$query = new PhabricatorAuthPasswordQuery();
|
||||||
|
return $this->revokeWithQuery($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function revokeCredentialsFrom($object) {
|
||||||
|
$query = id(new PhabricatorAuthPasswordQuery())
|
||||||
|
->withObjectPHIDs(array($object->getPHID()));
|
||||||
|
return $this->revokeWithQuery($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function revokeWithQuery(PhabricatorAuthPasswordQuery $query) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$passwords = $query
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withIsRevoked(false)
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$content_source = PhabricatorContentSource::newForSource(
|
||||||
|
PhabricatorDaemonContentSource::SOURCECONST);
|
||||||
|
|
||||||
|
$revoke_type = PhabricatorAuthPasswordRevokeTransaction::TRANSACTIONTYPE;
|
||||||
|
|
||||||
|
$auth_phid = id(new PhabricatorAuthApplication())->getPHID();
|
||||||
|
foreach ($passwords as $password) {
|
||||||
|
$xactions = array();
|
||||||
|
|
||||||
|
$xactions[] = $password->getApplicationTransactionTemplate()
|
||||||
|
->setTransactionType($revoke_type)
|
||||||
|
->setNewValue(true);
|
||||||
|
|
||||||
|
$editor = $password->getApplicationTransactionEditor()
|
||||||
|
->setActor($viewer)
|
||||||
|
->setActingAsPHID($auth_phid)
|
||||||
|
->setContinueOnNoEffect(true)
|
||||||
|
->setContinueOnMissingFields(true)
|
||||||
|
->setContentSource($content_source)
|
||||||
|
->applyTransactions($password, $xactions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count($passwords);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -37,7 +37,7 @@ final class PhabricatorAuthSSHRevoker
|
||||||
->setTransactionType(PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE)
|
->setTransactionType(PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE)
|
||||||
->setNewValue(1);
|
->setNewValue(1);
|
||||||
|
|
||||||
$editor = id(new PhabricatorAuthSSHKeyEditor())
|
$editor = $ssh_key->getApplicationTransactionEditor()
|
||||||
->setActor($viewer)
|
->setActor($viewer)
|
||||||
->setActingAsPHID($auth_phid)
|
->setActingAsPHID($auth_phid)
|
||||||
->setContinueOnNoEffect(true)
|
->setContinueOnNoEffect(true)
|
||||||
|
|
167
src/applications/auth/storage/PhabricatorAuthPassword.php
Normal file
167
src/applications/auth/storage/PhabricatorAuthPassword.php
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorAuthPassword
|
||||||
|
extends PhabricatorAuthDAO
|
||||||
|
implements
|
||||||
|
PhabricatorPolicyInterface,
|
||||||
|
PhabricatorDestructibleInterface,
|
||||||
|
PhabricatorApplicationTransactionInterface {
|
||||||
|
|
||||||
|
protected $objectPHID;
|
||||||
|
protected $passwordType;
|
||||||
|
protected $passwordHash;
|
||||||
|
protected $isRevoked;
|
||||||
|
|
||||||
|
private $object = self::ATTACHABLE;
|
||||||
|
|
||||||
|
const PASSWORD_TYPE_ACCOUNT = 'account';
|
||||||
|
const PASSWORD_TYPE_VCS = 'vcs';
|
||||||
|
const PASSWORD_TYPE_TEST = 'test';
|
||||||
|
|
||||||
|
public static function initializeNewPassword(
|
||||||
|
PhabricatorUser $object,
|
||||||
|
$type) {
|
||||||
|
|
||||||
|
return id(new self())
|
||||||
|
->setObjectPHID($object->getPHID())
|
||||||
|
->attachObject($object)
|
||||||
|
->setPasswordType($type)
|
||||||
|
->setIsRevoked(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getConfiguration() {
|
||||||
|
return array(
|
||||||
|
self::CONFIG_AUX_PHID => true,
|
||||||
|
self::CONFIG_COLUMN_SCHEMA => array(
|
||||||
|
'passwordType' => 'text64',
|
||||||
|
'passwordHash' => 'text128',
|
||||||
|
'isRevoked' => 'bool',
|
||||||
|
),
|
||||||
|
self::CONFIG_KEY_SCHEMA => array(
|
||||||
|
'key_role' => array(
|
||||||
|
'columns' => array('objectPHID', 'passwordType'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
) + parent::getConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPHIDType() {
|
||||||
|
return PhabricatorAuthPasswordPHIDType::TYPECONST;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getObject() {
|
||||||
|
return $this->assertAttached($this->object);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attachObject($object) {
|
||||||
|
$this->object = $object;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPassword(
|
||||||
|
PhutilOpaqueEnvelope $password,
|
||||||
|
PhabricatorUser $object) {
|
||||||
|
|
||||||
|
$hasher = PhabricatorPasswordHasher::getBestHasher();
|
||||||
|
|
||||||
|
$digest = $this->digestPassword($password, $object);
|
||||||
|
$hash = $hasher->getPasswordHashForStorage($digest);
|
||||||
|
$raw_hash = $hash->openEnvelope();
|
||||||
|
|
||||||
|
return $this->setPasswordHash($raw_hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function comparePassword(
|
||||||
|
PhutilOpaqueEnvelope $password,
|
||||||
|
PhabricatorUser $object) {
|
||||||
|
|
||||||
|
$digest = $this->digestPassword($password, $object);
|
||||||
|
$raw_hash = $this->getPasswordHash();
|
||||||
|
$hash = new PhutilOpaqueEnvelope($raw_hash);
|
||||||
|
|
||||||
|
return PhabricatorPasswordHasher::comparePassword($digest, $hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function digestPassword(
|
||||||
|
PhutilOpaqueEnvelope $password,
|
||||||
|
PhabricatorUser $object) {
|
||||||
|
|
||||||
|
$object_phid = $object->getPHID();
|
||||||
|
|
||||||
|
if ($this->getObjectPHID() !== $object->getPHID()) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'This password is associated with an object PHID ("%s") for '.
|
||||||
|
'a different object than the provided one ("%s").',
|
||||||
|
$this->getObjectPHID(),
|
||||||
|
$object->getPHID()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$raw_input = PhabricatorHash::digestPassword($password, $object_phid);
|
||||||
|
|
||||||
|
return new PhutilOpaqueEnvelope($raw_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function getCapabilities() {
|
||||||
|
return array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPolicy($capability) {
|
||||||
|
return PhabricatorPolicies::getMostOpenPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
|
||||||
|
return array(
|
||||||
|
array($this->getObject(), PhabricatorPolicyCapability::CAN_VIEW),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( PhabricatorDestructibleInterface )----------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function destroyObjectPermanently(
|
||||||
|
PhabricatorDestructionEngine $engine) {
|
||||||
|
$this->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function getApplicationTransactionEditor() {
|
||||||
|
return new PhabricatorAuthPasswordEditor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getApplicationTransactionObject() {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getApplicationTransactionTemplate() {
|
||||||
|
return new PhabricatorAuthPasswordTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function willRenderTimeline(
|
||||||
|
PhabricatorApplicationTransactionView $timeline,
|
||||||
|
AphrontRequest $request) {
|
||||||
|
|
||||||
|
return $timeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorAuthPasswordTransaction
|
||||||
|
extends PhabricatorApplicationTransaction {
|
||||||
|
|
||||||
|
public function getApplicationName() {
|
||||||
|
return 'auth';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getApplicationTransactionType() {
|
||||||
|
return PhabricatorAuthPasswordPHIDType::TYPECONST;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getApplicationTransactionCommentObject() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBaseTransactionClass() {
|
||||||
|
return 'PhabricatorAuthPasswordTransactionType';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorAuthPasswordRevokeTransaction
|
||||||
|
extends PhabricatorAuthPasswordTransactionType {
|
||||||
|
|
||||||
|
const TRANSACTIONTYPE = 'password.revoke';
|
||||||
|
|
||||||
|
public function generateOldValue($object) {
|
||||||
|
return (bool)$object->getIsRevoked();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateNewValue($object, $value) {
|
||||||
|
return (bool)$value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyInternalEffects($object, $value) {
|
||||||
|
$object->setIsRevoked((int)$value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle() {
|
||||||
|
if ($this->getNewValue()) {
|
||||||
|
return pht(
|
||||||
|
'%s revoked this password.',
|
||||||
|
$this->renderAuthor());
|
||||||
|
} else {
|
||||||
|
return pht(
|
||||||
|
'%s removed this password from the revocation list.',
|
||||||
|
$this->renderAuthor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
abstract class PhabricatorAuthPasswordTransactionType
|
||||||
|
extends PhabricatorModularTransactionType {}
|
Loading…
Reference in a new issue