1
0
Fork 0
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:
epriestley 2014-12-15 11:14:53 -08:00
parent 0507626f01
commit 288498f8d0
6 changed files with 149 additions and 13 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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