Something went wrong. Try again.
@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
Something went wrong. Try again.
8.9 kB · 305 lines
PHP
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306<?php
final class PhabricatorAuthSSHKeyEditor extends PhabricatorApplicationTransactionEditor {
private $isAdministrativeEdit;
public function setIsAdministrativeEdit($is_administrative_edit) { $this->isAdministrativeEdit = $is_administrative_edit; return $this; }
public function getIsAdministrativeEdit() { return $this->isAdministrativeEdit; }
public function getEditorApplicationClass() { return PhabricatorAuthApplication::class; }
public function getEditorObjectsDescription() { return pht('SSH Keys'); }
public function getTransactionTypes() { $types = parent::getTransactionTypes();
$types[] = PhabricatorAuthSSHKeyTransaction::TYPE_NAME; $types[] = PhabricatorAuthSSHKeyTransaction::TYPE_KEY; $types[] = PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE;
return $types; }
protected function getCustomTransactionOldValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) { case PhabricatorAuthSSHKeyTransaction::TYPE_NAME: return $object->getName(); case PhabricatorAuthSSHKeyTransaction::TYPE_KEY: return $object->getEntireKey(); case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE: return !$object->getIsActive(); }
}
protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) { case PhabricatorAuthSSHKeyTransaction::TYPE_NAME: case PhabricatorAuthSSHKeyTransaction::TYPE_KEY: return $xaction->getNewValue(); case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE: return (bool)$xaction->getNewValue(); } }
protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) {
$value = $xaction->getNewValue(); switch ($xaction->getTransactionType()) { case PhabricatorAuthSSHKeyTransaction::TYPE_NAME: $object->setName($value); return; case PhabricatorAuthSSHKeyTransaction::TYPE_KEY: $public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($value);
$type = $public_key->getType(); $body = $public_key->getBody(); $comment = $public_key->getComment();
$object->setKeyType($type); $object->setKeyBody($body); $object->setKeyComment($comment); return; case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE: if ($value) { $new = null; } else { $new = 1; }
$object->setIsActive($new); return; } }
protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { return; }
protected function validateTransaction( PhabricatorLiskDAO $object, $type, array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions); $viewer = $this->requireActor();
switch ($type) { case PhabricatorAuthSSHKeyTransaction::TYPE_NAME: $missing = $this->validateIsEmptyTextField( $object->getName(), $xactions);
if ($missing) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht('SSH key name is required.'), nonempty(last($xactions), null));
$error->setIsMissingFieldError(true); $errors[] = $error; } break;
case PhabricatorAuthSSHKeyTransaction::TYPE_KEY: $missing = $this->validateIsEmptyTextField( $object->getName(), $xactions);
if ($missing) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht('SSH key material is required.'), nonempty(last($xactions), null));
$error->setIsMissingFieldError(true); $errors[] = $error; } else { foreach ($xactions as $xaction) { $new = $xaction->getNewValue();
try { $public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($new); } catch (Exception $ex) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), $ex->getMessage(), $xaction); continue; }
// The database does not have a unique key on just the <keyBody> // column because we allow multiple accounts to revoke the same // key, so we can't rely on database constraints to prevent users // from adding keys that are on the revocation list back to their // accounts. Explicitly check for a revoked copy of the key.
$revoked_keys = id(new PhabricatorAuthSSHKeyQuery()) ->setViewer($viewer) ->withObjectPHIDs(array($object->getObjectPHID())) ->withIsActive(0) ->withKeys(array($public_key)) ->execute(); if ($revoked_keys) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Revoked'), pht( 'This key has been revoked. Choose or generate a new, '. 'unique key.'), $xaction); continue; } } } break;
case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE: foreach ($xactions as $xaction) { if (!$xaction->getNewValue()) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht('SSH keys can not be reactivated.'), $xaction); } } break; }
return $errors; }
protected function didCatchDuplicateKeyException( PhabricatorLiskDAO $object, array $xactions, Exception $ex) {
$errors = array(); $errors[] = new PhabricatorApplicationTransactionValidationError( PhabricatorAuthSSHKeyTransaction::TYPE_KEY, pht('Duplicate'), pht( 'This public key is already associated with another user or device. '. 'Each key must unambiguously identify a single unique owner.'), null);
throw new PhabricatorApplicationTransactionValidationException($errors); }
protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { return true; }
protected function getMailSubjectPrefix() { return pht('[SSH Key]'); }
protected function getMailThreadID(PhabricatorLiskDAO $object) { return 'ssh-key-'.$object->getPHID(); }
protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) {
// After making any change to an SSH key, drop the authfile cache so it // is regenerated the next time anyone authenticates. PhabricatorAuthSSHKeyQuery::deleteSSHKeyCache();
return $xactions; }
protected function getMailTo(PhabricatorLiskDAO $object) { return $object->getObject()->getSSHKeyNotifyPHIDs(); }
protected function getMailCC(PhabricatorLiskDAO $object) { return array(); }
protected function buildReplyHandler(PhabricatorLiskDAO $object) { return id(new PhabricatorAuthSSHKeyReplyHandler()) ->setMailReceiver($object); }
protected function buildMailTemplate(PhabricatorLiskDAO $object) { $id = $object->getID(); $name = $object->getName();
$mail = id(new PhabricatorMetaMTAMail()) ->setSubject(pht('SSH Key %d: %s', $id, $name));
// The primary value of this mail is alerting users to account compromises, // so force delivery. In particular, this mail should still be delivered // even if "self mail" is disabled. $mail->setForceDelivery(true);
return $mail; }
protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) {
$body = parent::buildMailBody($object, $xactions);
if (!$this->getIsAdministrativeEdit()) { $body->addTextSection( pht('SECURITY WARNING'), pht( 'If you do not recognize this change, it may indicate your account '. 'has been compromised.')); }
$detail_uri = $object->getURI(); $detail_uri = PhabricatorEnv::getProductionURI($detail_uri);
$body->addLinkSection(pht('SSH KEY DETAIL'), $detail_uri);
return $body; }
protected function getCustomWorkerState() { return array( 'isAdministrativeEdit' => $this->isAdministrativeEdit, ); }
protected function loadCustomWorkerState(array $state) { $this->isAdministrativeEdit = idx($state, 'isAdministrativeEdit'); return $this; }
}