diff --git a/resources/sql/autopatches/20140814.passphrasecredentialconduit.sql b/resources/sql/autopatches/20140814.passphrasecredentialconduit.sql new file mode 100644 index 0000000000..6dda0aaf6a --- /dev/null +++ b/resources/sql/autopatches/20140814.passphrasecredentialconduit.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_passphrase.passphrase_credential + ADD COLUMN allowConduit BOOL NOT NULL DEFAULT 0; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 94949bc310..c5b4e29b36 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -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', diff --git a/src/applications/passphrase/application/PhabricatorPassphraseApplication.php b/src/applications/passphrase/application/PhabricatorPassphraseApplication.php index 631448f18d..3deadcbd7f 100644 --- a/src/applications/passphrase/application/PhabricatorPassphraseApplication.php +++ b/src/applications/passphrase/application/PhabricatorPassphraseApplication.php @@ -46,6 +46,7 @@ final class PhabricatorPassphraseApplication extends PhabricatorApplication { 'reveal/(?P\d+)/' => 'PassphraseCredentialRevealController', 'public/(?P\d+)/' => 'PassphraseCredentialPublicController', 'lock/(?P\d+)/' => 'PassphraseCredentialLockController', + 'conduit/(?P\d+)/' => 'PassphraseCredentialConduitController', )); } diff --git a/src/applications/passphrase/conduit/PassphraseConduitAPIMethod.php b/src/applications/passphrase/conduit/PassphraseConduitAPIMethod.php new file mode 100644 index 0000000000..7b5ed82460 --- /dev/null +++ b/src/applications/passphrase/conduit/PassphraseConduitAPIMethod.php @@ -0,0 +1,10 @@ + 'optional list', + 'phids' => 'optional list', + 'needSecrets' => 'optional bool', + 'needPublicKeys' => 'optional bool', + ) + $this->getPagerParamTypes(); + } + + public function defineReturnType() { + return 'list'; + } + + 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); + } + +} diff --git a/src/applications/passphrase/controller/PassphraseCredentialConduitController.php b/src/applications/passphrase/controller/PassphraseCredentialConduitController.php new file mode 100644 index 0000000000..cea1aeac6a --- /dev/null +++ b/src/applications/passphrase/controller/PassphraseCredentialConduitController.php @@ -0,0 +1,81 @@ +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); + } + } + +} diff --git a/src/applications/passphrase/controller/PassphraseCredentialViewController.php b/src/applications/passphrase/controller/PassphraseCredentialViewController.php index ad0029ea03..12dcd64287 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialViewController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialViewController.php @@ -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) diff --git a/src/applications/passphrase/editor/PassphraseCredentialTransactionEditor.php b/src/applications/passphrase/editor/PassphraseCredentialTransactionEditor.php index 937ca33c2d..64db942aeb 100644 --- a/src/applications/passphrase/editor/PassphraseCredentialTransactionEditor.php +++ b/src/applications/passphrase/editor/PassphraseCredentialTransactionEditor.php @@ -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; diff --git a/src/applications/passphrase/query/PassphraseCredentialQuery.php b/src/applications/passphrase/query/PassphraseCredentialQuery.php index c45dbfabe5..ef09bfedb9 100644 --- a/src/applications/passphrase/query/PassphraseCredentialQuery.php +++ b/src/applications/passphrase/query/PassphraseCredentialQuery.php @@ -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); } diff --git a/src/applications/passphrase/storage/PassphraseCredential.php b/src/applications/passphrase/storage/PassphraseCredential.php index 20d2437094..241da9ca41 100644 --- a/src/applications/passphrase/storage/PassphraseCredential.php +++ b/src/applications/passphrase/storage/PassphraseCredential.php @@ -14,6 +14,7 @@ final class PassphraseCredential extends PassphraseDAO protected $secretID; protected $isDestroyed; protected $isLocked = 0; + protected $allowConduit = 0; private $secret = self::ATTACHABLE; diff --git a/src/applications/passphrase/storage/PassphraseCredentialTransaction.php b/src/applications/passphrase/storage/PassphraseCredentialTransaction.php index a8d918c785..0615bd9e90 100644 --- a/src/applications/passphrase/storage/PassphraseCredentialTransaction.php +++ b/src/applications/passphrase/storage/PassphraseCredentialTransaction.php @@ -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();