diff --git a/src/applications/conduit/controller/PhabricatorConduitTokenEditController.php b/src/applications/conduit/controller/PhabricatorConduitTokenEditController.php --- a/src/applications/conduit/controller/PhabricatorConduitTokenEditController.php +++ b/src/applications/conduit/controller/PhabricatorConduitTokenEditController.php @@ -83,11 +83,20 @@ ->addCancelButton($panel_uri); } else { $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( + ->setUser($viewer); + + if ($token->getTokenType() === PhabricatorConduitToken::TYPE_CLUSTER) { + $dialog->appendChild( + pht( + 'This token is automatically generated by Phabricator, and used '. + 'to make requests between nodes in a Phabricator cluster. You '. + 'can not use this token in external applications.')); + } else { + $form->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Token')) ->setValue($token->getToken())); + } $dialog ->appendForm($form) diff --git a/src/applications/conduit/query/PhabricatorConduitTokenQuery.php b/src/applications/conduit/query/PhabricatorConduitTokenQuery.php --- a/src/applications/conduit/query/PhabricatorConduitTokenQuery.php +++ b/src/applications/conduit/query/PhabricatorConduitTokenQuery.php @@ -7,6 +7,7 @@ private $objectPHIDs; private $expired; private $tokens; + private $tokenTypes; public function withExpired($expired) { $this->expired = $expired; @@ -28,6 +29,11 @@ return $this; } + public function withTokenTypes(array $types) { + $this->tokenTypes = $types; + return $this; + } + public function loadPage() { $table = new PhabricatorConduitToken(); $conn_r = $table->establishConnection('r'); @@ -67,6 +73,13 @@ $this->tokens); } + if ($this->tokenTypes !== null) { + $where[] = qsprintf( + $conn_r, + 'tokenType IN (%Ls)', + $this->tokenTypes); + } + if ($this->expired !== null) { if ($this->expired) { $where[] = qsprintf( diff --git a/src/applications/conduit/settings/PhabricatorConduitSettingsPanel.php b/src/applications/conduit/settings/PhabricatorConduitSettingsPanel.php --- a/src/applications/conduit/settings/PhabricatorConduitSettingsPanel.php +++ b/src/applications/conduit/settings/PhabricatorConduitSettingsPanel.php @@ -47,7 +47,7 @@ 'href' => '/conduit/token/edit/'.$token->getID().'/', 'sigil' => 'workflow', ), - substr($token->getToken(), 0, 8).'...'), + $token->getPublicTokenName()), PhabricatorConduitToken::getTokenTypeName($token->getTokenType()), phabricator_datetime($token->getDateCreated(), $viewer), ($token->getExpires() diff --git a/src/applications/conduit/storage/PhabricatorConduitToken.php b/src/applications/conduit/storage/PhabricatorConduitToken.php --- a/src/applications/conduit/storage/PhabricatorConduitToken.php +++ b/src/applications/conduit/storage/PhabricatorConduitToken.php @@ -12,8 +12,8 @@ private $object = self::ATTACHABLE; const TYPE_STANDARD = 'api'; - const TYPE_TEMPORARY = 'tmp'; const TYPE_COMMANDLINE = 'cli'; + const TYPE_CLUSTER = 'clr'; public function getConfiguration() { return array( @@ -37,6 +37,43 @@ ) + parent::getConfiguration(); } + public static function loadClusterTokenForUser(PhabricatorUser $user) { + if (!$user->isLoggedIn()) { + return null; + } + + $tokens = id(new PhabricatorConduitTokenQuery()) + ->setViewer($user) + ->withObjectPHIDs(array($user->getPHID())) + ->withTokenTypes(array(self::TYPE_CLUSTER)) + ->withExpired(false) + ->execute(); + + // Only return a token if it has at least 5 minutes left before + // expiration. Cluster tokens cycle regularly, so we don't want to use + // one that's going to expire momentarily. + $now = PhabricatorTime::getNow(); + $must_expire_after = $now + phutil_units('5 minutes in seconds'); + + foreach ($tokens as $token) { + if ($token->getExpires() > $must_expire_after) { + return $token; + } + } + + // We didn't find any existing tokens (or the existing tokens are all about + // to expire) so generate a new token. + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $token = PhabricatorConduitToken::initializeNewToken( + $user->getPHID(), + self::TYPE_CLUSTER); + $token->save(); + unset($unguarded); + + return $token; + } + public static function initializeNewToken($object_phid, $token_type) { $token = new PhabricatorConduitToken(); $token->objectPHID = $object_phid; @@ -53,8 +90,8 @@ public static function getTokenTypeName($type) { $map = array( self::TYPE_STANDARD => pht('Standard API Token'), - self::TYPE_TEMPORARY => pht('Temporary API Token'), self::TYPE_COMMANDLINE => pht('Command Line API Token'), + self::TYPE_CLUSTER => pht('Cluster API Token'), ); return idx($map, $type, $type); @@ -63,25 +100,35 @@ public static function getAllTokenTypes() { return array( self::TYPE_STANDARD, - self::TYPE_TEMPORARY, self::TYPE_COMMANDLINE, + self::TYPE_CLUSTER, ); } private function getTokenExpires($token_type) { + $now = PhabricatorTime::getNow(); switch ($token_type) { case self::TYPE_STANDARD: return null; - case self::TYPE_TEMPORARY: - return PhabricatorTime::getNow() + phutil_units('24 hours in seconds'); case self::TYPE_COMMANDLINE: - return PhabricatorTime::getNow() + phutil_units('1 hour in seconds'); + return $now + phutil_units('1 hour in seconds'); + case self::TYPE_CLUSTER: + return $now + phutil_units('30 minutes in seconds'); default: throw new Exception( pht('Unknown Conduit token type "%s"!', $token_type)); } } + public function getPublicTokenName() { + switch ($this->getTokenType()) { + case self::TYPE_CLUSTER: + return pht('Cluster API Token'); + default: + return substr($this->getToken(), 0, 8).'...'; + } + } + public function getObject() { return $this->assertAttached($this->object); } diff --git a/src/applications/diffusion/query/DiffusionQuery.php b/src/applications/diffusion/query/DiffusionQuery.php --- a/src/applications/diffusion/query/DiffusionQuery.php +++ b/src/applications/diffusion/query/DiffusionQuery.php @@ -112,11 +112,15 @@ $domain = id(new PhutilURI(PhabricatorEnv::getURI('/')))->getDomain(); - // TODO: This call needs authentication, which is blocked by T5955. + $client = id(new ConduitClient($uri)) + ->setHost($domain); - return id(new ConduitClient($uri)) - ->setHost($domain) - ->callMethodSynchronous($method, $params); + $token = PhabricatorConduitToken::loadClusterTokenForUser($user); + if ($token) { + $client->setConduitToken($token->getToken()); + } + + return $client->callMethodSynchronous($method, $params); } public function execute() {