@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
at recaptime-dev/main 305 lines 9.0 kB view raw
1<?php 2 3final class PhabricatorAuthSSHKeyEditor 4 extends PhabricatorApplicationTransactionEditor { 5 6 private $isAdministrativeEdit; 7 8 public function setIsAdministrativeEdit($is_administrative_edit) { 9 $this->isAdministrativeEdit = $is_administrative_edit; 10 return $this; 11 } 12 13 public function getIsAdministrativeEdit() { 14 return $this->isAdministrativeEdit; 15 } 16 17 public function getEditorApplicationClass() { 18 return PhabricatorAuthApplication::class; 19 } 20 21 public function getEditorObjectsDescription() { 22 return pht('SSH Keys'); 23 } 24 25 public function getTransactionTypes() { 26 $types = parent::getTransactionTypes(); 27 28 $types[] = PhabricatorAuthSSHKeyTransaction::TYPE_NAME; 29 $types[] = PhabricatorAuthSSHKeyTransaction::TYPE_KEY; 30 $types[] = PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE; 31 32 return $types; 33 } 34 35 protected function getCustomTransactionOldValue( 36 PhabricatorLiskDAO $object, 37 PhabricatorApplicationTransaction $xaction) { 38 39 switch ($xaction->getTransactionType()) { 40 case PhabricatorAuthSSHKeyTransaction::TYPE_NAME: 41 return $object->getName(); 42 case PhabricatorAuthSSHKeyTransaction::TYPE_KEY: 43 return $object->getEntireKey(); 44 case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE: 45 return !$object->getIsActive(); 46 } 47 48 } 49 50 protected function getCustomTransactionNewValue( 51 PhabricatorLiskDAO $object, 52 PhabricatorApplicationTransaction $xaction) { 53 54 switch ($xaction->getTransactionType()) { 55 case PhabricatorAuthSSHKeyTransaction::TYPE_NAME: 56 case PhabricatorAuthSSHKeyTransaction::TYPE_KEY: 57 return $xaction->getNewValue(); 58 case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE: 59 return (bool)$xaction->getNewValue(); 60 } 61 } 62 63 protected function applyCustomInternalTransaction( 64 PhabricatorLiskDAO $object, 65 PhabricatorApplicationTransaction $xaction) { 66 67 $value = $xaction->getNewValue(); 68 switch ($xaction->getTransactionType()) { 69 case PhabricatorAuthSSHKeyTransaction::TYPE_NAME: 70 $object->setName($value); 71 return; 72 case PhabricatorAuthSSHKeyTransaction::TYPE_KEY: 73 $public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($value); 74 75 $type = $public_key->getType(); 76 $body = $public_key->getBody(); 77 $comment = $public_key->getComment(); 78 79 $object->setKeyType($type); 80 $object->setKeyBody($body); 81 $object->setKeyComment($comment); 82 return; 83 case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE: 84 if ($value) { 85 $new = null; 86 } else { 87 $new = 1; 88 } 89 90 $object->setIsActive($new); 91 return; 92 } 93 } 94 95 protected function applyCustomExternalTransaction( 96 PhabricatorLiskDAO $object, 97 PhabricatorApplicationTransaction $xaction) { 98 return; 99 } 100 101 protected function validateTransaction( 102 PhabricatorLiskDAO $object, 103 $type, 104 array $xactions) { 105 106 $errors = parent::validateTransaction($object, $type, $xactions); 107 $viewer = $this->requireActor(); 108 109 switch ($type) { 110 case PhabricatorAuthSSHKeyTransaction::TYPE_NAME: 111 $missing = $this->validateIsEmptyTextField( 112 $object->getName(), 113 $xactions); 114 115 if ($missing) { 116 $error = new PhabricatorApplicationTransactionValidationError( 117 $type, 118 pht('Required'), 119 pht('SSH key name is required.'), 120 nonempty(last($xactions), null)); 121 122 $error->setIsMissingFieldError(true); 123 $errors[] = $error; 124 } 125 break; 126 127 case PhabricatorAuthSSHKeyTransaction::TYPE_KEY: 128 $missing = $this->validateIsEmptyTextField( 129 $object->getName(), 130 $xactions); 131 132 if ($missing) { 133 $error = new PhabricatorApplicationTransactionValidationError( 134 $type, 135 pht('Required'), 136 pht('SSH key material is required.'), 137 nonempty(last($xactions), null)); 138 139 $error->setIsMissingFieldError(true); 140 $errors[] = $error; 141 } else { 142 foreach ($xactions as $xaction) { 143 $new = $xaction->getNewValue(); 144 145 try { 146 $public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($new); 147 } catch (Exception $ex) { 148 $errors[] = new PhabricatorApplicationTransactionValidationError( 149 $type, 150 pht('Invalid'), 151 $ex->getMessage(), 152 $xaction); 153 continue; 154 } 155 156 // The database does not have a unique key on just the <keyBody> 157 // column because we allow multiple accounts to revoke the same 158 // key, so we can't rely on database constraints to prevent users 159 // from adding keys that are on the revocation list back to their 160 // accounts. Explicitly check for a revoked copy of the key. 161 162 $revoked_keys = id(new PhabricatorAuthSSHKeyQuery()) 163 ->setViewer($viewer) 164 ->withObjectPHIDs(array($object->getObjectPHID())) 165 ->withIsActive(0) 166 ->withKeys(array($public_key)) 167 ->execute(); 168 if ($revoked_keys) { 169 $errors[] = new PhabricatorApplicationTransactionValidationError( 170 $type, 171 pht('Revoked'), 172 pht( 173 'This key has been revoked. Choose or generate a new, '. 174 'unique key.'), 175 $xaction); 176 continue; 177 } 178 } 179 } 180 break; 181 182 case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE: 183 foreach ($xactions as $xaction) { 184 if (!$xaction->getNewValue()) { 185 $errors[] = new PhabricatorApplicationTransactionValidationError( 186 $type, 187 pht('Invalid'), 188 pht('SSH keys can not be reactivated.'), 189 $xaction); 190 } 191 } 192 break; 193 } 194 195 return $errors; 196 } 197 198 protected function didCatchDuplicateKeyException( 199 PhabricatorLiskDAO $object, 200 array $xactions, 201 Exception $ex) { 202 203 $errors = array(); 204 $errors[] = new PhabricatorApplicationTransactionValidationError( 205 PhabricatorAuthSSHKeyTransaction::TYPE_KEY, 206 pht('Duplicate'), 207 pht( 208 'This public key is already associated with another user or device. '. 209 'Each key must unambiguously identify a single unique owner.'), 210 null); 211 212 throw new PhabricatorApplicationTransactionValidationException($errors); 213 } 214 215 216 protected function shouldSendMail( 217 PhabricatorLiskDAO $object, 218 array $xactions) { 219 return true; 220 } 221 222 protected function getMailSubjectPrefix() { 223 return pht('[SSH Key]'); 224 } 225 226 protected function getMailThreadID(PhabricatorLiskDAO $object) { 227 return 'ssh-key-'.$object->getPHID(); 228 } 229 230 protected function applyFinalEffects( 231 PhabricatorLiskDAO $object, 232 array $xactions) { 233 234 // After making any change to an SSH key, drop the authfile cache so it 235 // is regenerated the next time anyone authenticates. 236 PhabricatorAuthSSHKeyQuery::deleteSSHKeyCache(); 237 238 return $xactions; 239 } 240 241 242 protected function getMailTo(PhabricatorLiskDAO $object) { 243 return $object->getObject()->getSSHKeyNotifyPHIDs(); 244 } 245 246 protected function getMailCC(PhabricatorLiskDAO $object) { 247 return array(); 248 } 249 250 protected function buildReplyHandler(PhabricatorLiskDAO $object) { 251 return id(new PhabricatorAuthSSHKeyReplyHandler()) 252 ->setMailReceiver($object); 253 } 254 255 protected function buildMailTemplate(PhabricatorLiskDAO $object) { 256 $id = $object->getID(); 257 $name = $object->getName(); 258 259 $mail = id(new PhabricatorMetaMTAMail()) 260 ->setSubject(pht('SSH Key %d: %s', $id, $name)); 261 262 // The primary value of this mail is alerting users to account compromises, 263 // so force delivery. In particular, this mail should still be delivered 264 // even if "self mail" is disabled. 265 $mail->setForceDelivery(true); 266 267 return $mail; 268 } 269 270 protected function buildMailBody( 271 PhabricatorLiskDAO $object, 272 array $xactions) { 273 274 $body = parent::buildMailBody($object, $xactions); 275 276 if (!$this->getIsAdministrativeEdit()) { 277 $body->addTextSection( 278 pht('SECURITY WARNING'), 279 pht( 280 'If you do not recognize this change, it may indicate your account '. 281 'has been compromised.')); 282 } 283 284 $detail_uri = $object->getURI(); 285 $detail_uri = PhabricatorEnv::getProductionURI($detail_uri); 286 287 $body->addLinkSection(pht('SSH KEY DETAIL'), $detail_uri); 288 289 return $body; 290 } 291 292 293 protected function getCustomWorkerState() { 294 return array( 295 'isAdministrativeEdit' => $this->isAdministrativeEdit, 296 ); 297 } 298 299 protected function loadCustomWorkerState(array $state) { 300 $this->isAdministrativeEdit = idx($state, 'isAdministrativeEdit'); 301 return $this; 302 } 303 304 305}