mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-18 11:30:55 +01:00
Implement passphrase.query
for querying credentials
Summary: Resolves T5868. This implements `passphrase.query` and a mechanism for allowing Conduit access to credentials. Test Plan: Tested locally. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: talshiri, epriestley, Korvin Maniphest Tasks: T5868 Differential Revision: https://secure.phabricator.com/D10262
This commit is contained in:
parent
300910f462
commit
26f283fe21
11 changed files with 274 additions and 0 deletions
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_passphrase.passphrase_credential
|
||||
ADD COLUMN allowConduit BOOL NOT NULL DEFAULT 0;
|
|
@ -1034,8 +1034,10 @@ phutil_register_library_map(array(
|
|||
'PackageMail' => 'applications/owners/mail/PackageMail.php',
|
||||
'PackageModifyMail' => 'applications/owners/mail/PackageModifyMail.php',
|
||||
'PassphraseAbstractKey' => 'applications/passphrase/keys/PassphraseAbstractKey.php',
|
||||
'PassphraseConduitAPIMethod' => 'applications/passphrase/conduit/PassphraseConduitAPIMethod.php',
|
||||
'PassphraseController' => 'applications/passphrase/controller/PassphraseController.php',
|
||||
'PassphraseCredential' => 'applications/passphrase/storage/PassphraseCredential.php',
|
||||
'PassphraseCredentialConduitController' => 'applications/passphrase/controller/PassphraseCredentialConduitController.php',
|
||||
'PassphraseCredentialControl' => 'applications/passphrase/view/PassphraseCredentialControl.php',
|
||||
'PassphraseCredentialCreateController' => 'applications/passphrase/controller/PassphraseCredentialCreateController.php',
|
||||
'PassphraseCredentialDestroyController' => 'applications/passphrase/controller/PassphraseCredentialDestroyController.php',
|
||||
|
@ -1059,6 +1061,7 @@ phutil_register_library_map(array(
|
|||
'PassphraseCredentialViewController' => 'applications/passphrase/controller/PassphraseCredentialViewController.php',
|
||||
'PassphraseDAO' => 'applications/passphrase/storage/PassphraseDAO.php',
|
||||
'PassphrasePasswordKey' => 'applications/passphrase/keys/PassphrasePasswordKey.php',
|
||||
'PassphraseQueryConduitAPIMethod' => 'applications/passphrase/conduit/PassphraseQueryConduitAPIMethod.php',
|
||||
'PassphraseRemarkupRule' => 'applications/passphrase/remarkup/PassphraseRemarkupRule.php',
|
||||
'PassphraseSSHKey' => 'applications/passphrase/keys/PassphraseSSHKey.php',
|
||||
'PassphraseSecret' => 'applications/passphrase/storage/PassphraseSecret.php',
|
||||
|
@ -3839,12 +3842,14 @@ phutil_register_library_map(array(
|
|||
'PackageMail' => 'PhabricatorMail',
|
||||
'PackageModifyMail' => 'PackageMail',
|
||||
'PassphraseAbstractKey' => 'Phobject',
|
||||
'PassphraseConduitAPIMethod' => 'ConduitAPIMethod',
|
||||
'PassphraseController' => 'PhabricatorController',
|
||||
'PassphraseCredential' => array(
|
||||
'PassphraseDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorDestructibleInterface',
|
||||
),
|
||||
'PassphraseCredentialConduitController' => 'PassphraseController',
|
||||
'PassphraseCredentialControl' => 'AphrontFormControl',
|
||||
'PassphraseCredentialCreateController' => 'PassphraseController',
|
||||
'PassphraseCredentialDestroyController' => 'PassphraseController',
|
||||
|
@ -3868,6 +3873,7 @@ phutil_register_library_map(array(
|
|||
'PassphraseCredentialViewController' => 'PassphraseController',
|
||||
'PassphraseDAO' => 'PhabricatorLiskDAO',
|
||||
'PassphrasePasswordKey' => 'PassphraseAbstractKey',
|
||||
'PassphraseQueryConduitAPIMethod' => 'PassphraseConduitAPIMethod',
|
||||
'PassphraseRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
||||
'PassphraseSSHKey' => 'PassphraseAbstractKey',
|
||||
'PassphraseSecret' => 'PassphraseDAO',
|
||||
|
|
|
@ -46,6 +46,7 @@ final class PhabricatorPassphraseApplication extends PhabricatorApplication {
|
|||
'reveal/(?P<id>\d+)/' => 'PassphraseCredentialRevealController',
|
||||
'public/(?P<id>\d+)/' => 'PassphraseCredentialPublicController',
|
||||
'lock/(?P<id>\d+)/' => 'PassphraseCredentialLockController',
|
||||
'conduit/(?P<id>\d+)/' => 'PassphraseCredentialConduitController',
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
abstract class PassphraseConduitAPIMethod extends ConduitAPIMethod {
|
||||
|
||||
final public function getApplication() {
|
||||
return PhabricatorApplication::getByClass(
|
||||
'PhabricatorPassphraseApplication');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseQueryConduitAPIMethod
|
||||
extends PassphraseConduitAPIMethod {
|
||||
|
||||
public function getAPIMethodName() {
|
||||
return 'passphrase.query';
|
||||
}
|
||||
|
||||
public function getMethodDescription() {
|
||||
return pht('Query credentials.');
|
||||
}
|
||||
|
||||
public function defineParamTypes() {
|
||||
return array(
|
||||
'ids' => 'optional list<int>',
|
||||
'phids' => 'optional list<phid>',
|
||||
'needSecrets' => 'optional bool',
|
||||
'needPublicKeys' => 'optional bool',
|
||||
) + $this->getPagerParamTypes();
|
||||
}
|
||||
|
||||
public function defineReturnType() {
|
||||
return 'list<dict>';
|
||||
}
|
||||
|
||||
public function defineErrorTypes() {
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function execute(ConduitAPIRequest $request) {
|
||||
$query = id(new PassphraseCredentialQuery())
|
||||
->setViewer($request->getUser());
|
||||
|
||||
if ($request->getValue('ids')) {
|
||||
$query->withIDs($request->getValue('ids'));
|
||||
}
|
||||
|
||||
if ($request->getValue('phids')) {
|
||||
$query->withPHIDs($request->getValue('phids'));
|
||||
}
|
||||
|
||||
if ($request->getValue('needSecrets')) {
|
||||
$query->needSecrets(true);
|
||||
}
|
||||
|
||||
$pager = $this->newPager($request);
|
||||
$credentials = $query->executeWithCursorPager($pager);
|
||||
|
||||
$results = array();
|
||||
foreach ($credentials as $credential) {
|
||||
$type = PassphraseCredentialType::getTypeByConstant(
|
||||
$credential->getCredentialType());
|
||||
if (!$type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$public_key = null;
|
||||
if ($request->getValue('needPublicKeys') && $type->hasPublicKey()) {
|
||||
$public_key = $type->getPublicKey(
|
||||
$request->getUser(),
|
||||
$credential);
|
||||
}
|
||||
|
||||
$secret = null;
|
||||
if ($request->getValue('needSecrets')) {
|
||||
if ($credential->getAllowConduit()) {
|
||||
$secret = $credential->getSecret()->openEnvelope();
|
||||
}
|
||||
}
|
||||
|
||||
$material = array();
|
||||
switch ($credential->getCredentialType()) {
|
||||
case PassphraseCredentialTypeSSHPrivateKeyFile::CREDENTIAL_TYPE:
|
||||
if ($secret) {
|
||||
$material['file'] = $secret;
|
||||
}
|
||||
if ($public_key) {
|
||||
$material['publicKey'] = $public_key;
|
||||
}
|
||||
break;
|
||||
case PassphraseCredentialTypeSSHPrivateKeyText::CREDENTIAL_TYPE:
|
||||
if ($secret) {
|
||||
$material['privateKey'] = $secret;
|
||||
}
|
||||
if ($public_key) {
|
||||
$material['publicKey'] = $public_key;
|
||||
}
|
||||
break;
|
||||
case PassphraseCredentialTypePassword::CREDENTIAL_TYPE:
|
||||
if ($secret) {
|
||||
$material['password'] = $secret;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$credential->getAllowConduit()) {
|
||||
$material['noAPIAccess'] = pht(
|
||||
'This credential\'s private material '.
|
||||
'is not accessible via API calls.');
|
||||
}
|
||||
|
||||
$results[$credential->getPHID()] = array(
|
||||
'id' => $credential->getID(),
|
||||
'phid' => $credential->getPHID(),
|
||||
'type' => $credential->getCredentialType(),
|
||||
'name' => $credential->getName(),
|
||||
'uri' =>
|
||||
PhabricatorEnv::getProductionURI('/'.$credential->getMonogram()),
|
||||
'monogram' => $credential->getMonogram(),
|
||||
'username' => $credential->getUsername(),
|
||||
'material' => $material,
|
||||
);
|
||||
}
|
||||
|
||||
$result = array(
|
||||
'data' => $results,
|
||||
);
|
||||
|
||||
return $this->addPagerResults($result, $pager);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseCredentialConduitController
|
||||
extends PassphraseController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = $data['id'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$credential = id(new PassphraseCredentialQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($this->id))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$credential) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$view_uri = '/K'.$credential->getID();
|
||||
|
||||
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
|
||||
$viewer,
|
||||
$request,
|
||||
$view_uri);
|
||||
|
||||
$type = PassphraseCredentialType::getTypeByConstant(
|
||||
$credential->getCredentialType());
|
||||
if (!$type) {
|
||||
throw new Exception(pht('Credential has invalid type "%s"!', $type));
|
||||
}
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$xactions = array();
|
||||
$xactions[] = id(new PassphraseCredentialTransaction())
|
||||
->setTransactionType(PassphraseCredentialTransaction::TYPE_CONDUIT)
|
||||
->setNewValue(!$credential->getAllowConduit());
|
||||
|
||||
$editor = id(new PassphraseCredentialTransactionEditor())
|
||||
->setActor($viewer)
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContentSourceFromRequest($request)
|
||||
->applyTransactions($credential, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($view_uri);
|
||||
}
|
||||
|
||||
if ($credential->getAllowConduit()) {
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Prevent Conduit access?'))
|
||||
->appendChild(
|
||||
pht(
|
||||
'This credential and its secret will no longer be able '.
|
||||
'to be retrieved using the `passphrase.query` method '.
|
||||
'in Conduit.'))
|
||||
->addSubmitButton(pht('Prevent Conduit Access'))
|
||||
->addCancelButton($view_uri);
|
||||
} else {
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Allow Conduit access?'))
|
||||
->appendChild(
|
||||
pht(
|
||||
'This credential will be able to be retrieved via the Conduit '.
|
||||
'API by users who have access to this credential. You should '.
|
||||
'only enable this for credentials which need to be accessed '.
|
||||
'programmatically (such as from build agents).'))
|
||||
->addSubmitButton(pht('Allow Conduit Access'))
|
||||
->addCancelButton($view_uri);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -93,6 +93,15 @@ final class PassphraseCredentialViewController extends PassphraseController {
|
|||
$credential_lock_icon = 'fa-unlock';
|
||||
}
|
||||
|
||||
$allow_conduit = $credential->getAllowConduit();
|
||||
if ($allow_conduit) {
|
||||
$credential_conduit_text = pht('Prevent Conduit Access');
|
||||
$credential_conduit_icon = 'fa-ban';
|
||||
} else {
|
||||
$credential_conduit_text = pht('Allow Conduit Access');
|
||||
$credential_conduit_icon = 'fa-wrench';
|
||||
}
|
||||
|
||||
$actions = id(new PhabricatorActionListView())
|
||||
->setObjectURI('/K'.$id)
|
||||
->setUser($viewer);
|
||||
|
@ -136,6 +145,13 @@ final class PassphraseCredentialViewController extends PassphraseController {
|
|||
->setWorkflow(true));
|
||||
}
|
||||
|
||||
$actions->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName($credential_conduit_text)
|
||||
->setIcon($credential_conduit_icon)
|
||||
->setHref($this->getApplicationURI("conduit/{$id}/"))
|
||||
->setWorkflow(true));
|
||||
|
||||
$actions->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName($credential_lock_text)
|
||||
|
|
|
@ -24,6 +24,7 @@ final class PassphraseCredentialTransactionEditor
|
|||
$types[] = PassphraseCredentialTransaction::TYPE_DESTROY;
|
||||
$types[] = PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET;
|
||||
$types[] = PassphraseCredentialTransaction::TYPE_LOCK;
|
||||
$types[] = PassphraseCredentialTransaction::TYPE_CONDUIT;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
@ -47,6 +48,8 @@ final class PassphraseCredentialTransactionEditor
|
|||
return (int)$object->getIsDestroyed();
|
||||
case PassphraseCredentialTransaction::TYPE_LOCK:
|
||||
return (int)$object->getIsLocked();
|
||||
case PassphraseCredentialTransaction::TYPE_CONDUIT:
|
||||
return (int)$object->getAllowConduit();
|
||||
case PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET:
|
||||
return null;
|
||||
}
|
||||
|
@ -67,6 +70,8 @@ final class PassphraseCredentialTransactionEditor
|
|||
case PassphraseCredentialTransaction::TYPE_DESTROY:
|
||||
case PassphraseCredentialTransaction::TYPE_LOCK:
|
||||
return (int)$xaction->getNewValue();
|
||||
case PassphraseCredentialTransaction::TYPE_CONDUIT:
|
||||
return (int)$xaction->getNewValue();
|
||||
}
|
||||
return parent::getCustomTransactionNewValue($object, $xaction);
|
||||
}
|
||||
|
@ -114,6 +119,9 @@ final class PassphraseCredentialTransactionEditor
|
|||
case PassphraseCredentialTransaction::TYPE_LOCK:
|
||||
$object->setIsLocked((int)$xaction->getNewValue());
|
||||
return;
|
||||
case PassphraseCredentialTransaction::TYPE_CONDUIT:
|
||||
$object->setAllowConduit((int)$xaction->getNewValue());
|
||||
return;
|
||||
}
|
||||
|
||||
return parent::applyCustomInternalTransaction($object, $xaction);
|
||||
|
@ -131,6 +139,7 @@ final class PassphraseCredentialTransactionEditor
|
|||
case PassphraseCredentialTransaction::TYPE_DESTROY:
|
||||
case PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET:
|
||||
case PassphraseCredentialTransaction::TYPE_LOCK:
|
||||
case PassphraseCredentialTransaction::TYPE_CONDUIT:
|
||||
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
||||
case PhabricatorTransactions::TYPE_EDIT_POLICY:
|
||||
return;
|
||||
|
|
|
@ -8,6 +8,7 @@ final class PassphraseCredentialQuery
|
|||
private $credentialTypes;
|
||||
private $providesTypes;
|
||||
private $isDestroyed;
|
||||
private $allowConduit;
|
||||
|
||||
private $needSecrets;
|
||||
|
||||
|
@ -36,6 +37,11 @@ final class PassphraseCredentialQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withAllowConduit($allow_conduit) {
|
||||
$this->allowConduit = $allow_conduit;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function needSecrets($need_secrets) {
|
||||
$this->needSecrets = $need_secrets;
|
||||
return $this;
|
||||
|
@ -127,6 +133,13 @@ final class PassphraseCredentialQuery
|
|||
(int)$this->isDestroyed);
|
||||
}
|
||||
|
||||
if ($this->allowConduit !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'allowConduit = %d',
|
||||
(int)$this->allowConduit);
|
||||
}
|
||||
|
||||
return $this->formatWhereClause($where);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ final class PassphraseCredential extends PassphraseDAO
|
|||
protected $secretID;
|
||||
protected $isDestroyed;
|
||||
protected $isLocked = 0;
|
||||
protected $allowConduit = 0;
|
||||
|
||||
private $secret = self::ATTACHABLE;
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ final class PassphraseCredentialTransaction
|
|||
const TYPE_DESTROY = 'passphrase:destroy';
|
||||
const TYPE_LOOKEDATSECRET = 'passphrase:lookedAtSecret';
|
||||
const TYPE_LOCK = 'passphrase:lock';
|
||||
const TYPE_CONDUIT = 'passphrase:conduit';
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'passphrase';
|
||||
|
@ -91,6 +92,17 @@ final class PassphraseCredentialTransaction
|
|||
return pht(
|
||||
'%s locked this credential.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
case self::TYPE_CONDUIT:
|
||||
if ($old) {
|
||||
return pht(
|
||||
'%s disallowed Conduit API access to this credential.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
} else {
|
||||
return pht(
|
||||
'%s allowed Conduit API access to this credential.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return parent::getTitle();
|
||||
|
|
Loading…
Reference in a new issue