mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-02 03:32:42 +01:00
Add conduit.getcapabilities and a modern CLI handshake workflow
Summary: Ref T5955. - Add `conduit.getcapabilities` to help arc (and other clients) determine formats, protocols, etc., the server supports. - Fixes T3117. Add a more modern version of the handshake workflow that allows all generated tokens to remain valid for an hour. - Generally, add a CLI token type. This token type expires after an hour when generated, then becomes permanent if used. Test Plan: - See D10988. - Ran `conduit.getcapabilities` and inspected output. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T3117, T5955 Differential Revision: https://secure.phabricator.com/D10989
This commit is contained in:
parent
0507626f01
commit
288498f8d0
6 changed files with 149 additions and 13 deletions
|
@ -199,6 +199,7 @@ phutil_register_library_map(array(
|
||||||
'ConduitConnectConduitAPIMethod' => 'applications/conduit/method/ConduitConnectConduitAPIMethod.php',
|
'ConduitConnectConduitAPIMethod' => 'applications/conduit/method/ConduitConnectConduitAPIMethod.php',
|
||||||
'ConduitConnectionGarbageCollector' => 'applications/conduit/garbagecollector/ConduitConnectionGarbageCollector.php',
|
'ConduitConnectionGarbageCollector' => 'applications/conduit/garbagecollector/ConduitConnectionGarbageCollector.php',
|
||||||
'ConduitException' => 'applications/conduit/protocol/exception/ConduitException.php',
|
'ConduitException' => 'applications/conduit/protocol/exception/ConduitException.php',
|
||||||
|
'ConduitGetCapabilitiesConduitAPIMethod' => 'applications/conduit/method/ConduitGetCapabilitiesConduitAPIMethod.php',
|
||||||
'ConduitGetCertificateConduitAPIMethod' => 'applications/conduit/method/ConduitGetCertificateConduitAPIMethod.php',
|
'ConduitGetCertificateConduitAPIMethod' => 'applications/conduit/method/ConduitGetCertificateConduitAPIMethod.php',
|
||||||
'ConduitLogGarbageCollector' => 'applications/conduit/garbagecollector/ConduitLogGarbageCollector.php',
|
'ConduitLogGarbageCollector' => 'applications/conduit/garbagecollector/ConduitLogGarbageCollector.php',
|
||||||
'ConduitMethodDoesNotExistException' => 'applications/conduit/protocol/exception/ConduitMethodDoesNotExistException.php',
|
'ConduitMethodDoesNotExistException' => 'applications/conduit/protocol/exception/ConduitMethodDoesNotExistException.php',
|
||||||
|
@ -1434,6 +1435,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorConduitToken' => 'applications/conduit/storage/PhabricatorConduitToken.php',
|
'PhabricatorConduitToken' => 'applications/conduit/storage/PhabricatorConduitToken.php',
|
||||||
'PhabricatorConduitTokenController' => 'applications/conduit/controller/PhabricatorConduitTokenController.php',
|
'PhabricatorConduitTokenController' => 'applications/conduit/controller/PhabricatorConduitTokenController.php',
|
||||||
'PhabricatorConduitTokenEditController' => 'applications/conduit/controller/PhabricatorConduitTokenEditController.php',
|
'PhabricatorConduitTokenEditController' => 'applications/conduit/controller/PhabricatorConduitTokenEditController.php',
|
||||||
|
'PhabricatorConduitTokenHandshakeController' => 'applications/conduit/controller/PhabricatorConduitTokenHandshakeController.php',
|
||||||
'PhabricatorConduitTokenQuery' => 'applications/conduit/query/PhabricatorConduitTokenQuery.php',
|
'PhabricatorConduitTokenQuery' => 'applications/conduit/query/PhabricatorConduitTokenQuery.php',
|
||||||
'PhabricatorConduitTokenTerminateController' => 'applications/conduit/controller/PhabricatorConduitTokenTerminateController.php',
|
'PhabricatorConduitTokenTerminateController' => 'applications/conduit/controller/PhabricatorConduitTokenTerminateController.php',
|
||||||
'PhabricatorConfigAllController' => 'applications/config/controller/PhabricatorConfigAllController.php',
|
'PhabricatorConfigAllController' => 'applications/config/controller/PhabricatorConfigAllController.php',
|
||||||
|
@ -3212,6 +3214,7 @@ phutil_register_library_map(array(
|
||||||
'ConduitConnectConduitAPIMethod' => 'ConduitAPIMethod',
|
'ConduitConnectConduitAPIMethod' => 'ConduitAPIMethod',
|
||||||
'ConduitConnectionGarbageCollector' => 'PhabricatorGarbageCollector',
|
'ConduitConnectionGarbageCollector' => 'PhabricatorGarbageCollector',
|
||||||
'ConduitException' => 'Exception',
|
'ConduitException' => 'Exception',
|
||||||
|
'ConduitGetCapabilitiesConduitAPIMethod' => 'ConduitAPIMethod',
|
||||||
'ConduitGetCertificateConduitAPIMethod' => 'ConduitAPIMethod',
|
'ConduitGetCertificateConduitAPIMethod' => 'ConduitAPIMethod',
|
||||||
'ConduitLogGarbageCollector' => 'PhabricatorGarbageCollector',
|
'ConduitLogGarbageCollector' => 'PhabricatorGarbageCollector',
|
||||||
'ConduitMethodDoesNotExistException' => 'ConduitMethodNotFoundException',
|
'ConduitMethodDoesNotExistException' => 'ConduitMethodNotFoundException',
|
||||||
|
@ -4557,6 +4560,7 @@ phutil_register_library_map(array(
|
||||||
),
|
),
|
||||||
'PhabricatorConduitTokenController' => 'PhabricatorConduitController',
|
'PhabricatorConduitTokenController' => 'PhabricatorConduitController',
|
||||||
'PhabricatorConduitTokenEditController' => 'PhabricatorConduitController',
|
'PhabricatorConduitTokenEditController' => 'PhabricatorConduitController',
|
||||||
|
'PhabricatorConduitTokenHandshakeController' => 'PhabricatorConduitController',
|
||||||
'PhabricatorConduitTokenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'PhabricatorConduitTokenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'PhabricatorConduitTokenTerminateController' => 'PhabricatorConduitController',
|
'PhabricatorConduitTokenTerminateController' => 'PhabricatorConduitController',
|
||||||
'PhabricatorConfigAllController' => 'PhabricatorConfigController',
|
'PhabricatorConfigAllController' => 'PhabricatorConfigController',
|
||||||
|
|
|
@ -50,6 +50,7 @@ final class PhabricatorConduitApplication extends PhabricatorApplication {
|
||||||
'PhabricatorConduitTokenEditController',
|
'PhabricatorConduitTokenEditController',
|
||||||
'token/terminate/(?:(?P<id>\d+)/)?' =>
|
'token/terminate/(?:(?P<id>\d+)/)?' =>
|
||||||
'PhabricatorConduitTokenTerminateController',
|
'PhabricatorConduitTokenTerminateController',
|
||||||
|
'login/' => 'PhabricatorConduitTokenHandshakeController',
|
||||||
),
|
),
|
||||||
'/api/(?P<method>[^/]+)' => 'PhabricatorConduitAPIController',
|
'/api/(?P<method>[^/]+)' => 'PhabricatorConduitAPIController',
|
||||||
);
|
);
|
||||||
|
|
|
@ -301,22 +301,22 @@ final class PhabricatorConduitAPIController
|
||||||
'ERR-INVALID-AUTH',
|
'ERR-INVALID-AUTH',
|
||||||
pht(
|
pht(
|
||||||
'API token "%s" has the wrong length. API tokens should be '.
|
'API token "%s" has the wrong length. API tokens should be '.
|
||||||
'32 characters long.'),
|
'32 characters long.',
|
||||||
|
$token_string),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$type = head(explode('-', $token_string));
|
$type = head(explode('-', $token_string));
|
||||||
switch ($type) {
|
$valid_types = PhabricatorConduitToken::getAllTokenTypes();
|
||||||
case 'api':
|
$valid_types = array_fuse($valid_types);
|
||||||
case 'tmp':
|
if (empty($valid_types[$type])) {
|
||||||
break;
|
return array(
|
||||||
default:
|
'ERR-INVALID-AUTH',
|
||||||
return array(
|
pht(
|
||||||
'ERR-INVALID-AUTH',
|
'API token "%s" has the wrong format. API tokens should be '.
|
||||||
pht(
|
'32 characters long and begin with one of these prefixes: %s.',
|
||||||
'API token "%s" has the wrong format. API tokens should begin '.
|
$token_string,
|
||||||
'with "api-" or "tmp-" and be 32 characters long.',
|
implode(', ', $valid_types)),
|
||||||
$token_string),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,6 +348,19 @@ final class PhabricatorConduitAPIController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this is a "cli-" token, it expires shortly after it is generated
|
||||||
|
// by default. Once it is actually used, we extend its lifetime and make
|
||||||
|
// it permanent. This allows stray tokens to get cleaned up automatically
|
||||||
|
// if they aren't being used.
|
||||||
|
if ($token->getTokenType() == PhabricatorConduitToken::TYPE_COMMANDLINE) {
|
||||||
|
if ($token->getExpires()) {
|
||||||
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||||
|
$token->setExpires(null);
|
||||||
|
$token->save();
|
||||||
|
unset($unguarded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$user = $token->getObject();
|
$user = $token->getObject();
|
||||||
if (!($user instanceof PhabricatorUser)) {
|
if (!($user instanceof PhabricatorUser)) {
|
||||||
return array(
|
return array(
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorConduitTokenHandshakeController
|
||||||
|
extends PhabricatorConduitController {
|
||||||
|
|
||||||
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
$viewer = $request->getViewer();
|
||||||
|
|
||||||
|
id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
|
||||||
|
$viewer,
|
||||||
|
$request,
|
||||||
|
'/');
|
||||||
|
|
||||||
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||||
|
$token = PhabricatorConduitToken::initializeNewToken(
|
||||||
|
$viewer->getPHID(),
|
||||||
|
PhabricatorConduitToken::TYPE_COMMANDLINE);
|
||||||
|
$token->save();
|
||||||
|
unset($unguarded);
|
||||||
|
|
||||||
|
$form = id(new AphrontFormView())
|
||||||
|
->setUser($viewer)
|
||||||
|
->appendRemarkupInstructions(
|
||||||
|
pht(
|
||||||
|
'Copy-paste the API Token below to grant access to your account.'))
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormTextControl())
|
||||||
|
->setLabel(pht('API Token'))
|
||||||
|
->setValue($token->getToken()))
|
||||||
|
->appendRemarkupInstructions(
|
||||||
|
pht(
|
||||||
|
'This will authorize the requesting script to act on your behalf '.
|
||||||
|
'permanently, like giving the script your account password.'))
|
||||||
|
->appendRemarkupInstructions(
|
||||||
|
pht(
|
||||||
|
'If you change your mind, you can revoke this token later in '.
|
||||||
|
'{nav icon=wrench,name=Settings > Conduit API Tokens}.'));
|
||||||
|
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle(pht('Grant Account Access'))
|
||||||
|
->setWidth(AphrontDialogView::WIDTH_FULL)
|
||||||
|
->appendForm($form)
|
||||||
|
->addCancelButton('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class ConduitGetCapabilitiesConduitAPIMethod extends ConduitAPIMethod {
|
||||||
|
|
||||||
|
public function getAPIMethodName() {
|
||||||
|
return 'conduit.getcapabilities';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shouldRequireAuthentication() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMethodDescription() {
|
||||||
|
return pht(
|
||||||
|
'List capabilities, wire formats, and authentication protocols '.
|
||||||
|
'available on this server.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function defineParamTypes() {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function defineReturnType() {
|
||||||
|
return 'dict<string, any>';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function defineErrorTypes() {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(ConduitAPIRequest $request) {
|
||||||
|
$authentication = array(
|
||||||
|
'token',
|
||||||
|
'asymmetric',
|
||||||
|
'session',
|
||||||
|
'sessionless',
|
||||||
|
);
|
||||||
|
|
||||||
|
$oauth_app = 'PhabricatorOAuthServerApplication';
|
||||||
|
if (PhabricatorApplication::isClassInstalled($oauth_app)) {
|
||||||
|
$authentication[] = 'oauth';
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'authentication' => $authentication,
|
||||||
|
'signatures' => array(
|
||||||
|
'consign',
|
||||||
|
),
|
||||||
|
'input' => array(
|
||||||
|
'json',
|
||||||
|
'urlencoded',
|
||||||
|
),
|
||||||
|
'output' => array(
|
||||||
|
'json',
|
||||||
|
'human',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ final class PhabricatorConduitToken
|
||||||
|
|
||||||
const TYPE_STANDARD = 'api';
|
const TYPE_STANDARD = 'api';
|
||||||
const TYPE_TEMPORARY = 'tmp';
|
const TYPE_TEMPORARY = 'tmp';
|
||||||
|
const TYPE_COMMANDLINE = 'cli';
|
||||||
|
|
||||||
public function getConfiguration() {
|
public function getConfiguration() {
|
||||||
return array(
|
return array(
|
||||||
|
@ -53,17 +54,28 @@ final class PhabricatorConduitToken
|
||||||
$map = array(
|
$map = array(
|
||||||
self::TYPE_STANDARD => pht('Standard API Token'),
|
self::TYPE_STANDARD => pht('Standard API Token'),
|
||||||
self::TYPE_TEMPORARY => pht('Temporary API Token'),
|
self::TYPE_TEMPORARY => pht('Temporary API Token'),
|
||||||
|
self::TYPE_COMMANDLINE => pht('Command Line API Token'),
|
||||||
);
|
);
|
||||||
|
|
||||||
return idx($map, $type, $type);
|
return idx($map, $type, $type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getAllTokenTypes() {
|
||||||
|
return array(
|
||||||
|
self::TYPE_STANDARD,
|
||||||
|
self::TYPE_TEMPORARY,
|
||||||
|
self::TYPE_COMMANDLINE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private function getTokenExpires($token_type) {
|
private function getTokenExpires($token_type) {
|
||||||
switch ($token_type) {
|
switch ($token_type) {
|
||||||
case self::TYPE_STANDARD:
|
case self::TYPE_STANDARD:
|
||||||
return null;
|
return null;
|
||||||
case self::TYPE_TEMPORARY:
|
case self::TYPE_TEMPORARY:
|
||||||
return PhabricatorTime::getNow() + phutil_units('24h in seconds');
|
return PhabricatorTime::getNow() + phutil_units('24 hours in seconds');
|
||||||
|
case self::TYPE_COMMANDLINE:
|
||||||
|
return PhabricatorTime::getNow() + phutil_units('1 hour in seconds');
|
||||||
default:
|
default:
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht('Unknown Conduit token type "%s"!', $token_type));
|
pht('Unknown Conduit token type "%s"!', $token_type));
|
||||||
|
|
Loading…
Reference in a new issue