3 final class PhabricatorAuthPassword
4 extends PhabricatorAuthDAO
6 PhabricatorPolicyInterface
,
7 PhabricatorDestructibleInterface
,
8 PhabricatorApplicationTransactionInterface
{
10 protected $objectPHID;
11 protected $passwordType;
12 protected $passwordHash;
13 protected $passwordSalt;
15 protected $legacyDigestFormat;
17 private $object = self
::ATTACHABLE
;
19 const PASSWORD_TYPE_ACCOUNT
= 'account';
20 const PASSWORD_TYPE_VCS
= 'vcs';
21 const PASSWORD_TYPE_TEST
= 'test';
23 public static function initializeNewPassword(
24 PhabricatorAuthPasswordHashInterface
$object,
28 ->setObjectPHID($object->getPHID())
29 ->attachObject($object)
30 ->setPasswordType($type)
34 protected function getConfiguration() {
36 self
::CONFIG_AUX_PHID
=> true,
37 self
::CONFIG_COLUMN_SCHEMA
=> array(
38 'passwordType' => 'text64',
39 'passwordHash' => 'text128',
40 'passwordSalt' => 'text64',
41 'isRevoked' => 'bool',
42 'legacyDigestFormat' => 'text32?',
44 self
::CONFIG_KEY_SCHEMA
=> array(
46 'columns' => array('objectPHID', 'passwordType'),
49 ) + parent
::getConfiguration();
52 public function getPHIDType() {
53 return PhabricatorAuthPasswordPHIDType
::TYPECONST
;
56 public function getObject() {
57 return $this->assertAttached($this->object);
60 public function attachObject($object) {
61 $this->object = $object;
65 public function getHasher() {
66 $hash = $this->newPasswordEnvelope();
67 return PhabricatorPasswordHasher
::getHasherForHash($hash);
70 public function canUpgrade() {
71 // If this password uses a legacy digest format, we can upgrade it to the
72 // new digest format even if a better hasher isn't available.
73 if ($this->getLegacyDigestFormat() !== null) {
77 $hash = $this->newPasswordEnvelope();
78 return PhabricatorPasswordHasher
::canUpgradeHash($hash);
81 public function upgradePasswordHasher(
82 PhutilOpaqueEnvelope
$envelope,
83 PhabricatorAuthPasswordHashInterface
$object) {
85 // Before we make changes, double check that this is really the correct
86 // password. It could be really bad if we "upgraded" a password and changed
89 if (!$this->comparePassword($envelope, $object)) {
92 'Attempting to upgrade password hasher, but the password for the '.
93 'upgrade is not the stored credential!'));
96 return $this->setPassword($envelope, $object);
99 public function setPassword(
100 PhutilOpaqueEnvelope
$password,
101 PhabricatorAuthPasswordHashInterface
$object) {
103 $hasher = PhabricatorPasswordHasher
::getBestHasher();
104 return $this->setPasswordWithHasher($password, $object, $hasher);
107 public function setPasswordWithHasher(
108 PhutilOpaqueEnvelope
$password,
109 PhabricatorAuthPasswordHashInterface
$object,
110 PhabricatorPasswordHasher
$hasher) {
112 if (!strlen($password->openEnvelope())) {
114 pht('Attempting to set an empty password!'));
117 // Generate (or regenerate) the salt first.
118 $new_salt = Filesystem
::readRandomCharacters(64);
119 $this->setPasswordSalt($new_salt);
121 // Clear any legacy digest format to force a modern digest.
122 $this->setLegacyDigestFormat(null);
124 $digest = $this->digestPassword($password, $object);
125 $hash = $hasher->getPasswordHashForStorage($digest);
126 $raw_hash = $hash->openEnvelope();
128 return $this->setPasswordHash($raw_hash);
131 public function comparePassword(
132 PhutilOpaqueEnvelope
$password,
133 PhabricatorAuthPasswordHashInterface
$object) {
135 $digest = $this->digestPassword($password, $object);
136 $hash = $this->newPasswordEnvelope();
138 return PhabricatorPasswordHasher
::comparePassword($digest, $hash);
141 public function newPasswordEnvelope() {
142 return new PhutilOpaqueEnvelope($this->getPasswordHash());
145 private function digestPassword(
146 PhutilOpaqueEnvelope
$password,
147 PhabricatorAuthPasswordHashInterface
$object) {
149 $object_phid = $object->getPHID();
151 if ($this->getObjectPHID() !== $object->getPHID()) {
154 'This password is associated with an object PHID ("%s") for '.
155 'a different object than the provided one ("%s").',
156 $this->getObjectPHID(),
157 $object->getPHID()));
160 $digest = $object->newPasswordDigest($password, $this);
162 if (!($digest instanceof PhutilOpaqueEnvelope
)) {
165 'Failed to digest password: object ("%s") did not return an '.
166 'opaque envelope with a password digest.',
167 $object->getPHID()));
175 /* -( PhabricatorPolicyInterface )----------------------------------------- */
178 public function getCapabilities() {
180 PhabricatorPolicyCapability
::CAN_VIEW
,
181 PhabricatorPolicyCapability
::CAN_EDIT
,
185 public function getPolicy($capability) {
186 return PhabricatorPolicies
::getMostOpenPolicy();
189 public function hasAutomaticCapability($capability, PhabricatorUser
$viewer) {
194 /* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
197 public function getExtendedPolicy($capability, PhabricatorUser
$viewer) {
199 array($this->getObject(), $capability),
204 /* -( PhabricatorDestructibleInterface )----------------------------------- */
207 public function destroyObjectPermanently(
208 PhabricatorDestructionEngine
$engine) {
213 /* -( PhabricatorApplicationTransactionInterface )------------------------- */
216 public function getApplicationTransactionEditor() {
217 return new PhabricatorAuthPasswordEditor();
220 public function getApplicationTransactionTemplate() {
221 return new PhabricatorAuthPasswordTransaction();