1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 19:40: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:
James Rhodes 2014-08-16 22:41:03 +10:00
parent 300910f462
commit 26f283fe21
11 changed files with 274 additions and 0 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_passphrase.passphrase_credential
ADD COLUMN allowConduit BOOL NOT NULL DEFAULT 0;

View file

@ -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',

View file

@ -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',
));
}

View file

@ -0,0 +1,10 @@
<?php
abstract class PassphraseConduitAPIMethod extends ConduitAPIMethod {
final public function getApplication() {
return PhabricatorApplication::getByClass(
'PhabricatorPassphraseApplication');
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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)

View file

@ -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;

View file

@ -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);
}

View file

@ -14,6 +14,7 @@ final class PassphraseCredential extends PassphraseDAO
protected $secretID;
protected $isDestroyed;
protected $isLocked = 0;
protected $allowConduit = 0;
private $secret = self::ATTACHABLE;

View file

@ -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();