Remove all "FileHasObject" edge reads and writes
[phabricator.git] / src / applications / auth / storage / PhabricatorAuthPassword.php
blob3196b58e60bbe98983d3bf60227d7c16afc007ca
1 <?php
3 final class PhabricatorAuthPassword
4 extends PhabricatorAuthDAO
5 implements
6 PhabricatorPolicyInterface,
7 PhabricatorDestructibleInterface,
8 PhabricatorApplicationTransactionInterface {
10 protected $objectPHID;
11 protected $passwordType;
12 protected $passwordHash;
13 protected $passwordSalt;
14 protected $isRevoked;
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,
25 $type) {
27 return id(new self())
28 ->setObjectPHID($object->getPHID())
29 ->attachObject($object)
30 ->setPasswordType($type)
31 ->setIsRevoked(0);
34 protected function getConfiguration() {
35 return array(
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(
45 'key_role' => 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;
62 return $this;
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) {
74 return true;
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
87 // the secret!
89 if (!$this->comparePassword($envelope, $object)) {
90 throw new Exception(
91 pht(
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())) {
113 throw new Exception(
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()) {
152 throw new Exception(
153 pht(
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)) {
163 throw new Exception(
164 pht(
165 'Failed to digest password: object ("%s") did not return an '.
166 'opaque envelope with a password digest.',
167 $object->getPHID()));
170 return $digest;
175 /* -( PhabricatorPolicyInterface )----------------------------------------- */
178 public function getCapabilities() {
179 return array(
180 PhabricatorPolicyCapability::CAN_VIEW,
181 PhabricatorPolicyCapability::CAN_EDIT,
185 public function getPolicy($capability) {
186 return PhabricatorPolicies::getMostOpenPolicy();
189 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
190 return false;
194 /* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
197 public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
198 return array(
199 array($this->getObject(), $capability),
204 /* -( PhabricatorDestructibleInterface )----------------------------------- */
207 public function destroyObjectPermanently(
208 PhabricatorDestructionEngine $engine) {
209 $this->delete();
213 /* -( PhabricatorApplicationTransactionInterface )------------------------- */
216 public function getApplicationTransactionEditor() {
217 return new PhabricatorAuthPasswordEditor();
220 public function getApplicationTransactionTemplate() {
221 return new PhabricatorAuthPasswordTransaction();